Code

4116b1c36e575ce3a03abcb54795c7f544bf67c7
[roundup.git] / roundup / backends / indexer_xapian.py
1 #$Id: indexer_xapian.py,v 1.6 2007-10-25 07:02:42 richard Exp $
2 ''' This implements the full-text indexer using the Xapian indexer.
3 '''
4 import re, os
6 import xapian
8 from roundup.backends.indexer_common import Indexer as IndexerBase
10 # TODO: we need to delete documents when a property is *reindexed*
12 class Indexer(IndexerBase):
13     def __init__(self, db):
14         IndexerBase.__init__(self, db)
15         self.db_path = db.config.DATABASE
16         self.reindex = 0
17         self.transaction_active = False
19     def _get_database(self):
20         index = os.path.join(self.db_path, 'text-index')
21         return xapian.WritableDatabase(index, xapian.DB_CREATE_OR_OPEN)
23     def save_index(self):
24         '''Save the changes to the index.'''
25         if not self.transaction_active:
26             return
27         database = self._get_database()
28         database.commit_transaction()
29         self.transaction_active = False
31     def close(self):
32         '''close the indexing database'''
33         pass
35     def rollback(self):
36         if not self.transaction_active:
37             return
38         database = self._get_database()
39         database.cancel_transaction()
40         self.transaction_active = False
42     def force_reindex(self):
43         '''Force a reindexing of the database.  This essentially
44         empties the tables ids and index and sets a flag so
45         that the databases are reindexed'''
46         self.reindex = 1
48     def should_reindex(self):
49         '''returns True if the indexes need to be rebuilt'''
50         return self.reindex
52     def add_text(self, identifier, text, mime_type='text/plain'):
53         ''' "identifier" is  (classname, itemid, property) '''
54         if mime_type != 'text/plain':
55             return
56         if not text: text = ''
58         # open the database and start a transaction if needed
59         database = self._get_database()
61         # XXX: Xapian now supports transactions, 
62         #  but there is a call to save_index() missing.
63         #if not self.transaction_active:
64             #database.begin_transaction()
65             #self.transaction_active = True
67         # TODO: allow configuration of other languages
68         stemmer = xapian.Stem("english")
70         # We use the identifier twice: once in the actual "text" being
71         # indexed so we can search on it, and again as the "data" being
72         # indexed so we know what we're matching when we get results
73         identifier = '%s:%s:%s'%identifier
75         # see if the id is in the database
76         enquire = xapian.Enquire(database)
77         query = xapian.Query(xapian.Query.OP_AND, [identifier])
78         enquire.set_query(query)
79         matches = enquire.get_mset(0, 10)
80         if len(matches):
81             docid = matches[0].docid
82         else:
83             docid = None
85         # create the new document
86         doc = xapian.Document()
87         doc.set_data(identifier)
88         doc.add_posting(identifier, 0)
90         for match in re.finditer(r'\b\w{%d,%d}\b'
91                                  % (self.minlength, self.maxlength),
92                                  text.upper()):
93             word = match.group(0)
94             if self.is_stopword(word):
95                 continue
96             term = stemmer(word)
97             doc.add_posting(term, match.start(0))
98         if docid:
99             database.replace_document(docid, doc)
100         else:
101             database.add_document(doc)
103     def find(self, wordlist):
104         '''look up all the words in the wordlist.
105         If none are found return an empty dictionary
106         * more rules here
107         '''
108         if not wordlist:
109             return {}
111         database = self._get_database()
113         enquire = xapian.Enquire(database)
114         stemmer = xapian.Stem("english")
115         terms = []
116         for term in [word.upper() for word in wordlist
117                           if self.minlength <= len(word) <= self.maxlength]:
118             if not self.is_stopword(term):
119                 terms.append(stemmer(term))
120         query = xapian.Query(xapian.Query.OP_AND, terms)
122         enquire.set_query(query)
123         matches = enquire.get_mset(0, 10)
125         return [tuple(m.document.get_data().split(':'))
126             for m in matches]