comparison roundup/hyperdb.py @ 3634:57c66056ffe4

Implemented what I'll call for now "transitive searching"... ...using the filter method. The first idea was mentioned on the roundup-users mailing list: http://article.gmane.org/gmane.comp.bug-tracking.roundup.user/6909 We can now search for items which link transitively to other classes using filter. An example is searching for all items where a certain user has added a message in the last week: db.issue.filter (None, {'messages.author' : '42', 'messages.date' : '.-1w;'}) or more readable (but not exactly semantically equivalent, if you're searching for multiple users in this way it will fail, because string searches are ANDed): {'messages.author.username':'ralf', ... We can even extend this further, look for all items that were changed by users belonging to a certain department (having the same supervisor -- a property that is not in the user class in standard roundup) in the last week, the filterspec would be: {'messages.author.supervisor' : '42', 'messages.date' : '.-1w;'} If anybody wants to suggest another name instead of transitive searching, you're welcome. I've implemented a generic method for this in hyperdb.py -- the backend now implements _filter in this case. With the generic method, anydbm and metakit should work (anydbm is tested, metakit breaks for other reasons). A backend may chose to implement the real transitive filter itself. This was done for rdbms_common.py. It now has an implementation of filter that supports transitive searching by creating one big join in the generated SQL query. I've added several new regression tests to test for the new features. All the tests (not just the new ones) run through on python2.3 and python2.4 with postgres, mysql, sqlite, anydbm -- but metakit was already broken when I started. I've generated a tag before commit called 'rsc_before_transitive_search' and will create the 'after' tag after this commit, so you can merge out my changes if you don't like them -- if you like them I can remove the tags. .-- Ralf
author Ralf Schlatterbeck <schlatterbeck@users.sourceforge.net>
date Sat, 08 Jul 2006 18:28:18 +0000
parents 77ed6c517793
children 53987aa153d2
comparison
equal deleted inserted replaced
3633:a292054b4393 3634:57c66056ffe4
13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS 13 # BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS" 14 # FOR A PARTICULAR PURPOSE. THE CODE PROVIDED HEREUNDER IS ON AN "AS IS"
15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE, 15 # BASIS, AND THERE IS NO OBLIGATION WHATSOEVER TO PROVIDE MAINTENANCE,
16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS. 16 # SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
17 # 17 #
18 # $Id: hyperdb.py,v 1.120 2006-05-06 17:18:03 a1s Exp $ 18 # $Id: hyperdb.py,v 1.121 2006-07-08 18:28:18 schlatterbeck Exp $
19 19
20 """Hyperdatabase implementation, especially field types. 20 """Hyperdatabase implementation, especially field types.
21 """ 21 """
22 __docformat__ = 'restructuredtext' 22 __docformat__ = 'restructuredtext'
23 23
24 # standard python modules 24 # standard python modules
25 import sys, os, time, re, shutil, weakref 25 import sys, os, time, re, shutil, weakref
26 26
27 # roundup modules 27 # roundup modules
28 import date, password 28 import date, password
29 from support import ensureParentsExist, PrioList 29 from support import ensureParentsExist, PrioList, Proptree
30 30
31 # 31 #
32 # Types 32 # Types
33 # 33 #
34 class String: 34 class String:
704 704
705 db.issue.find(messages={'1':1,'3':1}, files={'7':1}) 705 db.issue.find(messages={'1':1,'3':1}, files={'7':1})
706 """ 706 """
707 raise NotImplementedError 707 raise NotImplementedError
708 708
709 def _filter(self, search_matches, filterspec, sort=(None,None),
710 group=(None,None)):
711 """For some backends this implements the non-transitive
712 search, for more information see the filter method.
713 """
714 raise NotImplementedError
715
716 def _proptree (self, filterspec) :
717 """Build a tree of all transitive properties in the given
718 filterspec.
719 """
720 proptree = Proptree (self.db, self, '', self.getprops ())
721 for key, v in filterspec.iteritems () :
722 keys = key.split ('.')
723 p = proptree
724 for k in keys :
725 p = p.append (k)
726 p.val = v
727 return proptree
728
729 def _propsearch (self, search_matches, proptree, sort, group) :
730 """ Recursively search for the given properties in proptree.
731 Once all properties are non-transitive, the search generates a
732 simple _filter call which does the real work
733 """
734 for p in proptree.children :
735 if not p.children : continue
736 p.val = p.cls._propsearch (None, p, (None, None), (None, None))
737 filterspec = dict ([(p.name, p.val) for p in proptree.children])
738 return self._filter (search_matches, filterspec, sort, group)
739
709 def filter(self, search_matches, filterspec, sort=(None,None), 740 def filter(self, search_matches, filterspec, sort=(None,None),
710 group=(None,None)): 741 group=(None,None)):
711 """Return a list of the ids of the active nodes in this class that 742 """Return a list of the ids of the active nodes in this class that
712 match the 'filter' spec, sorted by the group spec and then the 743 match the 'filter' spec, sorted by the group spec and then the
713 sort spec. 744 sort spec.
714 745
715 "filterspec" is {propname: value(s)} 746 "filterspec" is {propname: value(s)}
716 747
748 Note that now the propname in filterspec may be transitive,
749 i.e., it may contain properties of the form link.link.link.name,
750 e.g. you can search for all issues where a message was added by
751 a certain user in the last week with a filterspec of
752 {'messages.author' : '42', 'messages.creation' : '.-1w;'}
753
717 "sort" and "group" are (dir, prop) where dir is '+', '-' or None 754 "sort" and "group" are (dir, prop) where dir is '+', '-' or None
718 and prop is a prop name or None 755 and prop is a prop name or None
719 756
720 "search_matches" is {nodeid: marker} 757 "search_matches" is {nodeid: marker}
721 758
722 The filter must match all properties specificed. If the property 759 The filter must match all properties specificed. If the property
723 value to match is a list: 760 value to match is a list:
724 761
725 1. String properties must match all elements in the list, and 762 1. String properties must match all elements in the list, and
726 2. Other properties must match any of the elements in the list. 763 2. Other properties must match any of the elements in the list.
727 """ 764
728 raise NotImplementedError 765 Implementation note:
766 This implements a non-optimized version of Transitive search
767 using _filter implemented in a backend class. A more efficient
768 version can be implemented in the individual backends -- e.g.,
769 an SQL backen will want to create a single SQL statement and
770 override the filter method instead of implementing _filter.
771 """
772 proptree = self._proptree (filterspec)
773 return self._propsearch (search_matches, proptree, sort, group)
729 774
730 def count(self): 775 def count(self):
731 """Get the number of nodes in this class. 776 """Get the number of nodes in this class.
732 777
733 If the returned integer is 'numnodes', the ids of all the nodes 778 If the returned integer is 'numnodes', the ids of all the nodes

Roundup Issue Tracker: http://roundup-tracker.org/