[Checkins] SVN: hurry.query/ Moving over from codespeak.net for easier maintenance.

Martijn Faassen faassen at infrae.com
Tue Sep 16 17:58:07 EDT 2008


Log message for revision 91197:
  Moving over from codespeak.net for easier maintenance.
  

Changed:
  A   hurry.query/
  A   hurry.query/trunk/
  A   hurry.query/trunk/.installed.cfg
  A   hurry.query/trunk/CHANGES.txt
  A   hurry.query/trunk/CREDITS.txt
  A   hurry.query/trunk/INSTALL.txt
  A   hurry.query/trunk/README.txt
  A   hurry.query/trunk/ZopePublicLicense.txt
  A   hurry.query/trunk/bin/
  A   hurry.query/trunk/bin/test
  A   hurry.query/trunk/buildout.cfg
  A   hurry.query/trunk/develop-eggs/
  A   hurry.query/trunk/develop-eggs/hurry.query.egg-link
  A   hurry.query/trunk/parts/
  A   hurry.query/trunk/parts/test/
  A   hurry.query/trunk/setup.py
  A   hurry.query/trunk/src/
  A   hurry.query/trunk/src/hurry/
  A   hurry.query/trunk/src/hurry/__init__.py
  A   hurry.query/trunk/src/hurry/query/
  A   hurry.query/trunk/src/hurry/query/__init__.py
  A   hurry.query/trunk/src/hurry/query/configure.zcml
  A   hurry.query/trunk/src/hurry/query/interfaces.py
  A   hurry.query/trunk/src/hurry/query/query.py
  A   hurry.query/trunk/src/hurry/query/query.txt
  A   hurry.query/trunk/src/hurry/query/set.py
  A   hurry.query/trunk/src/hurry/query/tests.py
  A   hurry.query/trunk/src/hurry/query/value.py

-=-
Added: hurry.query/trunk/.installed.cfg
===================================================================
--- hurry.query/trunk/.installed.cfg	                        (rev 0)
+++ hurry.query/trunk/.installed.cfg	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,20 @@
+[buildout]
+installed_develop_eggs = /home/faassen/buildout/hurry.query/develop-eggs/hurry.query.egg-link
+parts = test
+
+[test]
+__buildout_installed__ = /home/faassen/buildout/hurry.query/parts/test
+	/home/faassen/buildout/hurry.query/bin/test
+__buildout_signature__ = zc.recipe.testrunner-1.1.0-py2.4.egg zc.recipe.egg-1.1.0-py2.4.egg setuptools-0.6c8-py2.4.egg zope.testing-3.6.0-py2.4.egg zc.buildout-1.0.6-py2.4.egg zc.buildout-1.0.6-py2.4.egg zope.interface-3.4.1-py2.4-linux-i686.egg
+_b = /home/faassen/buildout/hurry.query/bin
+_d = /home/faassen/buildout/hurry.query/develop-eggs
+_e = /home/faassen/.buildout/eggs
+bin-directory = /home/faassen/buildout/hurry.query/bin
+defaults = ['--tests-pattern', '^f?tests$', '-v']
+develop-eggs-directory = /home/faassen/buildout/hurry.query/develop-eggs
+eggs = hurry.query
+eggs-directory = /home/faassen/.buildout/eggs
+executable = /home/faassen/bin/python2.4
+location = /home/faassen/buildout/hurry.query/parts/test
+recipe = zc.recipe.testrunner
+script = /home/faassen/buildout/hurry.query/bin/test

Added: hurry.query/trunk/CHANGES.txt
===================================================================
--- hurry.query/trunk/CHANGES.txt	                        (rev 0)
+++ hurry.query/trunk/CHANGES.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,24 @@
+hurry.query changes
+===================
+
+0.9.2 (2006-09-22)
+------------------
+
+* First release on the cheeseshop.
+
+0.9.1 (2006-06-16)
+------------------
+
+* Make zc.catalog a dependency of hurry.query.
+
+0.9 (2006-05-16)
+----------------
+
+* Separate hurry.query from the other hurry packages. Eggification work.
+
+* Support for ValueIndex from zc.catalog.
+
+0.8 (2006-05-01)
+----------------
+
+Initial public release.

Added: hurry.query/trunk/CREDITS.txt
===================================================================
--- hurry.query/trunk/CREDITS.txt	                        (rev 0)
+++ hurry.query/trunk/CREDITS.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+Credits
+-------
+
+Martijn Faassen - initial and main developer
+Stephan Richter - additional hurry.query index support
+Jan-Wijbrand Kolman - suggestions and feedback
+
+The hurry.query library for Zope 3 was developed at Infrae
+(http://www.infrae.com).

Added: hurry.query/trunk/INSTALL.txt
===================================================================
--- hurry.query/trunk/INSTALL.txt	                        (rev 0)
+++ hurry.query/trunk/INSTALL.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,13 @@
+Installation
+------------
+
+To install hurry.query, just add it to your project's setup.py and
+install it (by re-running buildout, for instance).
+
+You can incldue hurry.query's ZCML by doing::
+
+  <include package="hurry.query" />
+
+Or you can use z3c.autoinclude.
+
+

Added: hurry.query/trunk/README.txt
===================================================================
--- hurry.query/trunk/README.txt	                        (rev 0)
+++ hurry.query/trunk/README.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,4 @@
+hurry.query - higher level query system built on top of the Zope 3
+              catalog. Some inspiration came from Dieter Maurer's
+              AdvancedQuery. See src/hurry/query/query.txt for
+              documentation.

Added: hurry.query/trunk/ZopePublicLicense.txt
===================================================================
--- hurry.query/trunk/ZopePublicLicense.txt	                        (rev 0)
+++ hurry.query/trunk/ZopePublicLicense.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,54 @@
+Zope Public License (ZPL) Version 2.1
+-------------------------------------
+
+A copyright notice accompanies this license document that
+identifies the copyright holders.
+
+This license has been certified as open source. It has also
+been designated as GPL compatible by the Free Software
+Foundation (FSF).
+
+Redistribution and use in source and binary forms, with or
+without modification, are permitted provided that the
+following conditions are met:
+
+1. Redistributions in source code must retain the
+   accompanying copyright notice, this list of conditions,
+   and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the accompanying
+   copyright notice, this list of conditions, and the
+   following disclaimer in the documentation and/or other
+   materials provided with the distribution.
+
+3. Names of the copyright holders must not be used to
+   endorse or promote products derived from this software
+   without prior written permission from the copyright
+   holders.
+
+4. The right to distribute this software or to use it for
+   any purpose does not give you the right to use
+   Servicemarks (sm) or Trademarks (tm) of the copyright
+   holders. Use of them is covered by separate agreement
+   with the copyright holders.
+
+5. If any files are modified, you must cause the modified
+   files to carry prominent notices stating that you changed
+   the files and the date of any change.
+
+Disclaimer
+
+  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``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 THE COPYRIGHT HOLDERS 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.

Added: hurry.query/trunk/bin/test
===================================================================
--- hurry.query/trunk/bin/test	                        (rev 0)
+++ hurry.query/trunk/bin/test	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,101 @@
+#!/home/faassen/bin/python2.4
+
+import sys
+sys.path[0:0] = [
+  '/home/faassen/buildout/hurry.query/src',
+  '/home/faassen/.buildout/eggs/zope.testing-3.6.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.interface-3.4.1-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/setuptools-0.6c8-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zc.catalog-1.2.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.security-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.schema-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.publisher-3.4.2-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.i18nmessageid-3.4.3-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.component-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.container-3.5.3-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.app.catalog-3.5.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/pytz-2007k-py2.4.egg',
+  '/home/faassen/.buildout/eggs/ZODB3-3.8.0-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.proxy-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.location-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.exceptions-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.deferredimport-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.configuration-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.event-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.deprecation-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.testing-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.i18n-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.copypastemove-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.broken-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.dublincore-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.traversing-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.size-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.filerepresentation-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.lifecycleevent-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.dottedname-3.4.2-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.cachedescriptors-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.zapi-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.publisher-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.index-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.intid-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.component-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.annotation-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zdaemon-2.0.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/ZConfig-2.5.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.security-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.publication-3.4.3-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.folder-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.dependable-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.debug-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.authentication-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.principalannotation-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.appsetup-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.applicationcontrol-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.interface-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.pagetemplate-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.datetime-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.contenttype-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.pagetemplate-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.keyreference-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.thread-3.4-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.formlib-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.form-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.exception-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.error-3.5.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.http-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.error-3.5.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.session-3.5.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zodbcode-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.tal-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.tales-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.zcmlfiles-3.4.3-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.hookable-3.4.0-py2.4-linux-i686.egg',
+  '/home/faassen/.buildout/eggs/zope.app.basicskin-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.session-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.minmax-1.1.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/RestrictedPython-3.4.2-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.schema-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.wsgi-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.rotterdam-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.zopeappgenerations-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.locales-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.i18n-3.4.4-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.generations-3.4.1-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.content-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.modulealias-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.app.renderer-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/zope.structuredtext-3.4.0-py2.4.egg',
+  '/home/faassen/.buildout/eggs/docutils-0.4-py2.4.egg',
+  ]
+
+import os
+sys.argv[0] = os.path.abspath(sys.argv[0])
+os.chdir('/home/faassen/buildout/hurry.query/parts/test')
+
+
+import zope.testing.testrunner
+
+if __name__ == '__main__':
+    zope.testing.testrunner.run((['--tests-pattern', '^f?tests$', '-v']) + [
+  '--test-path', '/home/faassen/buildout/hurry.query/src',
+  ])


Property changes on: hurry.query/trunk/bin/test
___________________________________________________________________
Name: svn:executable
   + 

Added: hurry.query/trunk/buildout.cfg
===================================================================
--- hurry.query/trunk/buildout.cfg	                        (rev 0)
+++ hurry.query/trunk/buildout.cfg	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+[buildout]
+develop = . 
+parts = test
+newest = false
+
+[test]
+recipe = zc.recipe.testrunner
+eggs = hurry.query
+defaults = ['--tests-pattern', '^f?tests$', '-v']

Added: hurry.query/trunk/develop-eggs/hurry.query.egg-link
===================================================================
--- hurry.query/trunk/develop-eggs/hurry.query.egg-link	                        (rev 0)
+++ hurry.query/trunk/develop-eggs/hurry.query.egg-link	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,2 @@
+/home/faassen/buildout/hurry.query/src
+../
\ No newline at end of file

Added: hurry.query/trunk/setup.py
===================================================================
--- hurry.query/trunk/setup.py	                        (rev 0)
+++ hurry.query/trunk/setup.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,27 @@
+from setuptools import setup, find_packages
+
+setup(
+    name="hurry.query",
+    version="0.9.4dev",
+    packages=find_packages('src'),
+    package_dir= {'':'src'},
+    
+    namespace_packages=['hurry'],
+    package_data = {
+    '': ['*.txt', '*.zcml'],
+    },
+
+    zip_safe=False,
+    author='Infrae',
+    author_email='faassen at startifact.com',
+    description="""\
+hurry.query is a higher level query system built on top of the Zope 3
+catalog. It makes it easy to perform catalog queries in Zope 3 code.
+""",
+    license='ZPL 2.1',
+    keywords="zope zope3",
+    classifiers = ['Framework :: Zope3'],
+    install_requires=[
+    'zc.catalog >= 0.1.1',
+    'setuptools'],
+    )

Added: hurry.query/trunk/src/hurry/__init__.py
===================================================================
--- hurry.query/trunk/src/hurry/__init__.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/__init__.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,7 @@
+# this is a namespace package
+try:
+    import pkg_resources
+    pkg_resources.declare_namespace(__name__)
+except ImportError:
+    import pkgutil
+    __path__ = pkgutil.extend_path(__path__, __name__)

Added: hurry.query/trunk/src/hurry/query/__init__.py
===================================================================
--- hurry.query/trunk/src/hurry/query/__init__.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/__init__.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,3 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from query import And, Or, Eq, NotEq, Between, In, Ge, Le, Text

Added: hurry.query/trunk/src/hurry/query/configure.zcml
===================================================================
--- hurry.query/trunk/src/hurry/query/configure.zcml	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/configure.zcml	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,9 @@
+<configure 
+  xmlns="http://namespaces.zope.org/zope">
+
+  <utility
+    provides=".interfaces.IQuery"
+    factory=".query.Query"
+    />
+
+</configure>

Added: hurry.query/trunk/src/hurry/query/interfaces.py
===================================================================
--- hurry.query/trunk/src/hurry/query/interfaces.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/interfaces.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,11 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zope.interface import Interface
+
+class IQuery(Interface):
+    def searchResults(query):
+        """Query indexes.
+
+        Argument is a query composed of terms.
+        """
+

Added: hurry.query/trunk/src/hurry/query/query.py
===================================================================
--- hurry.query/trunk/src/hurry/query/query.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/query.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,200 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zope.interface import implements
+
+from zope.app import zapi
+from zope.app.intid.interfaces import IIntIds
+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 BTrees.IFBTree import weightedIntersection, union, difference, IFBTree
+
+from hurry.query import interfaces
+
+# XXX look into using multiunion for performance?
+
+class Query(object):
+    implements(interfaces.IQuery)
+
+    def searchResults(self, query):
+        results = query.apply()
+        if results is not None:
+            uidutil = zapi.getUtility(IIntIds)
+            results = ResultSet(results, uidutil)
+        return results
+
+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
+            # XXX should this be possible?
+            return IFBTree()
+
+        results.sort()
+
+        _, result = results.pop(0)
+        for _, r in results:
+            _, 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
+            # XXX should this be possible?
+            return IFBTree()
+
+        result = results.pop(0)
+        for r in results:
+            result = union(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 = zapi.getUtility(IIntIds)
+        result = IFBTree()
+        for uid in intids:
+            result.insert(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 = zapi.getUtility(ICatalog, self.catalog_name)
+        index = catalog[self.index_name]
+        return 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)
+
+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 IFBTree()
+
+        result = results.pop(0)
+        for r in results:
+            result = union(result, r)
+        return result
+

Added: hurry.query/trunk/src/hurry/query/query.txt
===================================================================
--- hurry.query/trunk/src/hurry/query/query.txt	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/query.txt	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,393 @@
+Hurry Query
+===========
+
+The hurry query system for the Zope 3 catalog builds on catalog
+indexes as defined in Zope 3 core, as well as the indexes in
+zc.catalog. It is in part inspired by AdvancedQuery for Zope 2 by
+Dieter Maurer, though has an independent origin.
+
+Setup
+-----
+
+Let's define a simple content object. First its interface::
+
+  >>> from zope.interface import Interface, Attribute, implements
+  >>> class IContent(Interface):
+  ...     f1 = Attribute('f1')
+  ...     f2 = Attribute('f2')
+  ...     f3 = Attribute('f3')
+  ...     f4 = Attribute('f4')
+  ...     t1 = Attribute('t1')
+  ...     t2 = Attribute('t2')
+
+And its implementation::
+
+  >>> from zope.app.container.contained import Contained
+  >>> class Content(Contained):
+  ...     implements(IContent)
+  ...     def __init__(self, id, f1='', f2='', f3='', f4='', t1='', t2=''):
+  ...         self.id = id
+  ...         self.f1 = f1
+  ...         self.f2 = f2
+  ...         self.f3 = f3
+  ...         self.f4 = f4
+  ...         self.t1 = t1
+  ...         self.t2 = t2
+  ...     def __cmp__(self, other):
+  ...         return cmp(self.id, other.id)
+
+The id attribute is just so we can identify objects we find again
+easily. By including the __cmp__ method we make sure search results
+can be stably sorted.
+
+We use a fake int id utility here so we can test independent of
+the full-blown zope environment::
+
+  >>> from zope import interface
+  >>> import zope.app.intid.interfaces
+  >>> from zope.app.testing import ztapi
+  >>> class DummyIntId(object):
+  ...     interface.implements(zope.app.intid.interfaces.IIntIds)
+  ...     MARKER = '__dummy_int_id__'
+  ...     def __init__(self):
+  ...         self.counter = 0
+  ...         self.data = {}
+  ...     def register(self, obj):
+  ...         intid = getattr(obj, self.MARKER, None)
+  ...         if intid is None:
+  ...             setattr(obj, self.MARKER, self.counter)
+  ...             self.data[self.counter] = obj
+  ...             intid = self.counter
+  ...             self.counter += 1
+  ...         return intid
+  ...     def getObject(self, intid):
+  ...         return self.data[intid]
+  ...     def __iter__(self):
+  ...         return iter(self.data)
+  >>> intid = DummyIntId()
+  >>> ztapi.provideUtility(
+  ...     zope.app.intid.interfaces.IIntIds, intid)
+
+Now let's register a catalog::
+
+  >>> from zope.app.catalog.interfaces import ICatalog
+  >>> from zope.app.catalog.catalog import Catalog
+  >>> catalog = Catalog()
+  >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+
+And set it up with various indexes::
+
+  >>> from zope.app.catalog.field import FieldIndex
+  >>> from zope.app.catalog.text import TextIndex
+  >>> catalog['f1'] = FieldIndex('f1', IContent)
+  >>> catalog['f2'] = FieldIndex('f2', IContent)
+  >>> catalog['f3'] = FieldIndex('f3', IContent)
+  >>> catalog['f4'] = FieldIndex('f4', IContent)
+  >>> catalog['t1'] = TextIndex('t1', IContent)
+  >>> catalog['t2'] = TextIndex('t2', IContent)
+
+Now let's create some objects so that they'll be cataloged::
+
+  >>> content = [
+  ... Content(1, 'a', 'b', 'd'),
+  ... Content(2, 'a', 'c'),
+  ... Content(3, 'X', 'c'),
+  ... Content(4, 'a', 'b', 'e'),
+  ... Content(5, 'X', 'b', 'e'),
+  ... Content(6, 'Y', 'Z')]
+
+And catalog them now::
+
+  >>> for entry in content:
+  ...     catalog.index_doc(intid.register(entry), entry)
+
+Now let's register a query utility::
+
+  >>> from hurry.query.query import Query
+  >>> from hurry.query.interfaces import IQuery
+  >>> ztapi.provideUtility(IQuery, Query())
+
+Set up some code to make querying and display the result
+easy::
+
+  >>> from zope.app import zapi
+  >>> from hurry.query.interfaces import IQuery
+  >>> def displayQuery(q):
+  ...     query = zapi.getUtility(IQuery)
+  ...     r = query.searchResults(q)
+  ...     return [e.id for e in sorted(list(r))]
+
+FieldIndex Queries
+------------------
+
+Now for a query where f1 equals a::
+
+  >>> from hurry.query import Eq
+  >>> f1 = ('catalog1', 'f1')
+  >>> displayQuery(Eq(f1, 'a'))
+  [1, 2, 4]
+
+Not equals (this is more efficient than the generic ~ operator)::
+
+  >>> from hurry.query import NotEq
+  >>> displayQuery(NotEq(f1, 'a'))
+  [3, 5, 6]
+
+Testing whether a field is in a set::
+
+  >>> from hurry.query import In
+  >>> displayQuery(In(f1, ['a', 'X']))
+  [1, 2, 3, 4, 5]
+
+Whether documents are in a specified range::
+
+  >>> from hurry.query import Between
+  >>> displayQuery(Between(f1, 'X', 'Y'))
+  [3, 5, 6]
+
+You can leave out one end of the range::
+
+  >>> displayQuery(Between(f1, 'X', None)) # 'X' < 'a'
+  [1, 2, 3, 4, 5, 6]
+  >>> displayQuery(Between(f1, None, 'X'))
+  [3, 5]
+
+You can also use greater-equals and lesser-equals for the same purpose::
+
+  >>> from hurry.query import Ge, Le
+  >>> displayQuery(Ge(f1, 'X'))
+  [1, 2, 3, 4, 5, 6]
+  >>> displayQuery(Le(f1, 'X'))
+  [3, 5]
+
+It's also possible to use not with the ~ operator::
+
+  >>> displayQuery(~Eq(f1, 'a'))
+  [3, 5, 6]
+
+Using and (&)::
+
+  >>> f2 = ('catalog1', 'f2')
+  >>> displayQuery(Eq(f1, 'a') & Eq(f2, 'b'))
+  [1, 4]
+
+Using or (|)::
+
+  >>> displayQuery(Eq(f1, 'a') | Eq(f2, 'b'))
+  [1, 2, 4, 5]
+
+These can be chained::
+
+  >>> displayQuery(Eq(f1, 'a') & Eq(f2, 'b') & Between(f1, 'a', 'b'))
+  [1, 4]
+  >>> displayQuery(Eq(f1, 'a') | Eq(f1, 'X') | Eq(f2, 'b'))
+  [1, 2, 3, 4, 5]
+
+And nested::
+
+  >>> displayQuery((Eq(f1, 'a') | Eq(f1, 'X')) & (Eq(f2, 'b') | Eq(f2, 'c')))
+  [1, 2, 3, 4, 5]
+
+"and" and "or" can also be spelled differently::
+
+  >>> from hurry.query import And, Or
+  >>> displayQuery(And(Eq(f1, 'a'), Eq(f2, 'b')))
+  [1, 4]
+  >>> displayQuery(Or(Eq(f1, 'a'), Eq(f2, 'b')))
+  [1, 2, 4, 5]
+
+Combination of In and &
+-----------------------
+
+A combination of 'In' and '&'::
+
+  >>> displayQuery(In(f1, ['a', 'X', 'Y', 'Z']))
+  [1, 2, 3, 4, 5, 6]
+  >>> displayQuery(In(f1, ['Z']))
+  []
+  >>> displayQuery(In(f1, ['a', 'X', 'Y', 'Z']) & In(f1, ['Z']))
+  []
+
+
+SetIndex queries
+----------------
+
+The SetIndex is defined in zc.catalog. Let's make a catalog which uses
+it::
+
+  >>> intid = DummyIntId()
+  >>> ztapi.provideUtility(
+  ...     zope.app.intid.interfaces.IIntIds, intid)
+  >>> from zope.app.catalog.interfaces import ICatalog
+  >>> from zope.app.catalog.catalog import Catalog
+  >>> catalog = Catalog()
+  >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+  >>> from zc.catalog.catalogindex import SetIndex
+  >>> catalog['f1'] = SetIndex('f1', IContent)
+  >>> catalog['f2'] = FieldIndex('f2', IContent)
+
+First let's set up some new data::
+
+  >>> content = [
+  ... Content(1, ['a', 'b', 'c'], 1),
+  ... Content(2, ['a'], 1),
+  ... Content(3, ['b'], 1),
+  ... Content(4, ['c', 'd'], 2),
+  ... Content(5, ['b', 'c'], 2),
+  ... Content(6, ['a', 'c'], 2)]
+
+And catalog them now::
+
+  >>> for entry in content:
+  ...     catalog.index_doc(intid.register(entry), entry)
+
+Now do a a 'any of' query, which returns all documents that
+contain any of the values listed::
+
+  >>> from hurry.query.set import AnyOf
+  >>> displayQuery(AnyOf(f1, ['a', 'c']))
+  [1, 2, 4, 5, 6]
+  >>> displayQuery(AnyOf(f1, ['c', 'b']))
+  [1, 3, 4, 5, 6]
+  >>> displayQuery(AnyOf(f1, ['a']))
+  [1, 2, 6]
+
+Do a 'all of' query, which returns all documents that
+contain all of the values listed::
+
+  >>> from hurry.query.set import AllOf
+  >>> displayQuery(AllOf(f1, ['a']))
+  [1, 2, 6]
+  >>> displayQuery(AllOf(f1, ['a', 'b']))
+  [1]
+  >>> displayQuery(AllOf(f1, ['a', 'c']))
+  [1, 6]
+
+We can combine this with other queries::
+
+  >>> displayQuery(AnyOf(f1, ['a']) & Eq(f2, 1))
+  [1, 2]
+
+
+ValueIndex queries
+------------------
+
+The ``ValueIndex`` is defined in ``zc.catalog`` and provides a generalization
+of the standard field index.
+
+  >>> from hurry.query import value
+
+Let's set up a catalog that uses this index. The ``ValueIndex`` is defined in
+``zc.catalog``. Let's make a catalog which uses it:
+
+  >>> intid = DummyIntId()
+  >>> ztapi.provideUtility(zope.app.intid.interfaces.IIntIds, intid)
+
+  >>> from zope.app.catalog.interfaces import ICatalog
+  >>> from zope.app.catalog.catalog import Catalog
+  >>> catalog = Catalog()
+  >>> ztapi.provideUtility(ICatalog, catalog, 'catalog1')
+
+  >>> from zc.catalog.catalogindex import ValueIndex
+  >>> catalog['f1'] = ValueIndex('f1', IContent)
+
+Next we set up some content data to fill the indices:
+
+  >>> content = [
+  ... Content(1, 'a'),
+  ... Content(2, 'b'),
+  ... Content(3, 'c'),
+  ... Content(4, 'd'),
+  ... Content(5, 'c'),
+  ... Content(6, 'a')]
+
+And catalog them now:
+
+  >>> for entry in content:
+  ...     catalog.index_doc(intid.register(entry), entry)
+
+
+Let's now query for all objects where ``f1`` equals 'a':
+
+  >>> f1 = ('catalog1', 'f1')
+  >>> displayQuery(value.Eq(f1, 'a'))
+  [1, 6]
+
+Next, let's find all objects where ``f1`` does not equal 'a'; this is more
+efficient than the generic ``~`` operator:
+
+  >>> displayQuery(value.NotEq(f1, 'a'))
+  [2, 3, 4, 5]
+
+You can also query for all objects where the value of ``f1`` is in a set of
+values:
+
+  >>> displayQuery(value.In(f1, ['a', 'd']))
+  [1, 4, 6]
+
+The next interesting set of queries allows you to make evaluations of the
+values. For example, you can ask for all objects between a certain set of
+values:
+
+  >>> displayQuery(value.Between(f1, 'a', 'c'))
+  [1, 2, 3, 5, 6]
+
+  >>> displayQuery(value.Between(f1, 'a', 'c', exclude_min=True))
+  [2, 3, 5]
+
+  >>> displayQuery(value.Between(f1, 'a', 'c', exclude_max=True))
+  [1, 2, 6]
+
+  >>> displayQuery(value.Between(f1, 'a', 'c',
+  ...                            exclude_min=True, exclude_max=True))
+  [2]
+
+You can also leave out one end of the range:
+
+  >>> displayQuery(value.Between(f1, 'c', None))
+  [3, 4, 5]
+  >>> displayQuery(value.Between(f1, None, 'c'))
+  [1, 2, 3, 5, 6]
+
+You can also use greater-equals and lesser-equals for the same purpose:
+
+  >>> displayQuery(value.Ge(f1, 'c'))
+  [3, 4, 5]
+  >>> displayQuery(value.Le(f1, 'c'))
+  [1, 2, 3, 5, 6]
+
+Of course, you can chain those queries with the others as demonstrated before.
+
+The ``value`` module also supports ``zc.catalog`` extents. The first query is
+``ExtentAny``, which returns all douments matching the extent. If the the
+extent is ``None``, all document ids are returned:
+
+  >>> displayQuery(value.ExtentAny(f1, None))
+  [1, 2, 3, 4, 5, 6]
+
+If we now create an extent that is only in the scope of the first four
+documents,
+
+  >>> from zc.catalog.extentcatalog import FilterExtent
+  >>> extent = FilterExtent(lambda extent, uid, obj: True)
+  >>> for i in range(4):
+  ...     extent.add(i, i)
+
+then only the first four are returned:
+
+  >>> displayQuery(value.ExtentAny(f1, extent))
+  [1, 2, 3, 4]
+
+The opposite query is the ``ExtentNone`` query, which returns all ids in the
+extent that are *not* in the index:
+
+  >>> id = intid.register(Content(7, 'b'))
+  >>> id = intid.register(Content(8, 'c'))
+  >>> id = intid.register(Content(9, 'a'))
+
+  >>> extent = FilterExtent(lambda extent, uid, obj: True)
+  >>> for i in range(9):
+  ...     extent.add(i, i)
+
+  >>> displayQuery(value.ExtentNone(f1, extent))
+  [7, 8, 9]

Added: hurry.query/trunk/src/hurry/query/set.py
===================================================================
--- hurry.query/trunk/src/hurry/query/set.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/set.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,56 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zc.catalog.interfaces import ISetIndex
+from hurry.query import query
+
+class SetTerm(query.IndexTerm):
+    def getIndex(self):
+        index = super(SetTerm, self).getIndex()
+        assert ISetIndex.providedBy(index)
+        return index
+
+class AnyOf(SetTerm):
+    def __init__(self, index_id, values):
+        super(AnyOf, self).__init__(index_id)
+        self.values = values
+
+    def apply(self):
+        return self.getIndex().apply({'any_of': self.values})
+
+class AllOf(SetTerm):
+    def __init__(self, index_id, values):
+        super(AllOf, self).__init__(index_id)
+        self.values = values
+
+    def apply(self):
+        return self.getIndex().apply({'all_of': self.values})
+
+class SetBetween(SetTerm):
+    def __init__(self, index_id,
+                 minimum=None, maximum=None,
+                 include_minimum=False, include_maximum=False):
+        super(SetBetween, self).__init__(index_id)
+        self.tuple = (minimum, maximum, include_minimum, include_maximum)
+
+    def apply(self):
+        return self.getIndex().apply({'between': self.tuple})
+
+class ExtentAny(SetTerm):
+    """Any ids in the extent that are indexed by this index.
+    """
+    def __init__(self, index_id, extent):
+        super(Any, self).__init__(index_id)
+        self.extent = extent
+
+    def apply(self):
+        return self.getIndex().apply({'any': self.extent})
+
+class ExtentNone(SetTerm):
+    """Any ids in the extent that are not indexed by this index.
+    """
+    def __init__(self, extent):
+        super(None, self).__init__(index_id)
+        self.extent = extent
+
+    def apply(self):
+        return self.getIndex().apply({'none': self.extent})

Added: hurry.query/trunk/src/hurry/query/tests.py
===================================================================
--- hurry.query/trunk/src/hurry/query/tests.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/tests.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,12 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+import unittest
+from zope.testing import doctest
+
+def test_suite():
+    return unittest.TestSuite((
+        doctest.DocFileSuite('query.txt'),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Added: hurry.query/trunk/src/hurry/query/value.py
===================================================================
--- hurry.query/trunk/src/hurry/query/value.py	                        (rev 0)
+++ hurry.query/trunk/src/hurry/query/value.py	2008-09-16 21:58:06 UTC (rev 91197)
@@ -0,0 +1,81 @@
+# Copyright (c) 2007 Infrae. All rights reserved.
+# See also LICENSE.txt
+from zc.catalog.interfaces import IValueIndex
+from hurry.query import query
+
+class ValueTerm(query.IndexTerm):
+    def getIndex(self):
+        index = super(ValueTerm, self).getIndex()
+        assert IValueIndex.providedBy(index)
+        return index
+
+class Eq(ValueTerm):
+    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({'any_of': (self.value,)})
+
+class NotEq(ValueTerm):
+    def __init__(self, index_id, not_value):
+        super(NotEq, self).__init__(index_id)
+        self.not_value = not_value
+
+    def apply(self):
+        index = self.getIndex()
+        values = list(index.values())
+        values.remove(self.not_value)
+        return index.apply({'any_of': values})
+
+class Between(ValueTerm):
+    def __init__(self, index_id, min_value=None, max_value=None,
+                 exclude_min=False, exclude_max=False):
+        super(Between, self).__init__(index_id)
+        self.min_value = min_value
+        self.max_value = max_value
+        self.exclude_min = exclude_min
+        self.exclude_max = exclude_max
+
+    def apply(self):
+        return self.getIndex().apply(
+            {'between': (self.min_value, self.max_value,
+                         self.exclude_min, self.exclude_max)})
+
+class Ge(Between):
+    def __init__(self, index_id, min_value):
+        super(Ge, self).__init__(index_id, min_value=min_value)
+
+class Le(Between):
+    def __init__(self, index_id, max_value):
+        super(Le, self).__init__(index_id, max_value=max_value)
+
+class In(ValueTerm):
+    def __init__(self, index_id, values):
+        assert None not in values
+        super(In, self).__init__(index_id)
+        self.values = values
+
+    def apply(self):
+        return self.getIndex().apply({'any_of': self.values})
+
+class ExtentAny(ValueTerm):
+    """Any ids in the extent that are indexed by this index.
+    """
+    def __init__(self, index_id, extent):
+        super(ExtentAny, self).__init__(index_id)
+        self.extent = extent
+
+    def apply(self):
+        return self.getIndex().apply({'any': self.extent})
+
+class ExtentNone(ValueTerm):
+    """Any ids in the extent that are not indexed by this index.
+    """
+    def __init__(self, index_id, extent):
+        super(ExtentNone, self).__init__(index_id)
+        self.extent = extent
+
+    def apply(self):
+        return self.getIndex().apply({'none': self.extent})



More information about the Checkins mailing list