Mercurial > p > roundup > code
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 |
