Commits

Matt Chaput committed 80dd7ea

Added self.is_closed check to reader methods to give better error messages.
Fixes issue #333.

Comments (0)

Files changed (4)

src/whoosh/filedb/compound.py

 
 from whoosh.compat import BytesIO, memoryview_
 from whoosh.filedb.structfile import BufferFile, StructFile
-from whoosh.filedb.filestore import FileStorage
+from whoosh.filedb.filestore import FileStorage, StorageError
 from whoosh.system import emptybytes
 from whoosh.util import random_name
 
     def __init__(self, dbfile, use_mmap=True, basepos=0):
         self._file = dbfile
         self._file.seek(basepos)
+        self.is_closed = False
 
         self._diroffset = self._file.read_long()
         self._dirlength = self._file.read_int()
         return "<%s (%s)>" % (self.__class__.__name__, self._name)
 
     def close(self):
+        if self.is_closed:
+            raise Exception("Already closed")
+        self.is_closed = True
+
         if self._source:
             try:
                 self._source.close()
             fileinfo = self._dir[name]
         except KeyError:
             raise NameError("Unknown file %r" % (name,))
-        return (fileinfo["offset"], fileinfo["length"])
+        return fileinfo["offset"], fileinfo["length"]
 
     def open_file(self, name, *args, **kwargs):
+        if self.is_closed:
+            raise StorageError("Storage was closed")
+
         offset, length = self.range(name)
         if self._source:
             # Create a memoryview/buffer from the mmap

src/whoosh/filedb/filestore.py

 
 # Exceptions
 
-class ReadOnlyError(Exception):
+class StorageError(Exception):
+    pass
+
+
+class ReadOnlyError(StorageError):
     pass
 
 

src/whoosh/reading.py

 
 # Exceptions
 
+class ReaderClosed(Exception):
+    """Exception raised when you try to do some operation on a closed searcher
+    (or a Results object derived from a searcher that has since been closed).
+    """
+
+    message = "Operation on a closed reader"
+
+
 class TermNotFound(Exception):
     pass
 
         return self._storage
 
     def has_deletions(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.has_deletions()
 
     def doc_count(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.doc_count()
 
     def doc_count_all(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.doc_count_all()
 
     def is_deleted(self, docnum):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.is_deleted(docnum)
 
     def generation(self):
                                self._segment)
 
     def __contains__(self, term):
+        if self.is_closed:
+            raise ReaderClosed
         fieldname, text = term
         if fieldname not in self.schema:
             return False
         return (fieldname, text) in self._terms
 
     def close(self):
+        if self.is_closed:
+            raise ReaderClosed("Reader already closed")
         self._terms.close()
         self._perdoc.close()
         if self._graph:
         self.is_closed = True
 
     def stored_fields(self, docnum):
+        if self.is_closed:
+            raise ReaderClosed
         assert docnum >= 0
         schema = self.schema
         sfs = self._perdoc.stored_fields(docnum)
     # Delegate doc methods to the per-doc reader
 
     def all_doc_ids(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.all_doc_ids()
 
     def iter_docs(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.iter_docs()
 
     def all_stored_fields(self):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.all_stored_fields()
 
     def field_length(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.field_length(fieldname)
 
     def min_field_length(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.min_field_length(fieldname)
 
     def max_field_length(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.max_field_length(fieldname)
 
     def doc_field_length(self, docnum, fieldname, default=0):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.doc_field_length(docnum, fieldname, default)
 
     def has_vector(self, docnum, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         return self._perdoc.has_vector(docnum, fieldname)
 
     #
 
     def _test_field(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         if fieldname not in self.schema:
             raise TermNotFound("No field %r" % fieldname)
         if self.schema[fieldname].format is None:
             raise TermNotFound("Field %r is not indexed" % fieldname)
 
     def all_terms(self):
+        if self.is_closed:
+            raise ReaderClosed
         schema = self.schema
         return ((fieldname, text) for fieldname, text in self._terms.terms()
                 if fieldname in schema)
         return IndexReader.lexicon(self, fieldname)
 
     def __iter__(self):
+        if self.is_closed:
+            raise ReaderClosed
         schema = self.schema
         return ((term, terminfo) for term, terminfo in self._terms.items()
                 if term[0] in schema)
 
     def iter_from(self, fieldname, text):
+        self._test_field(fieldname)
         schema = self.schema
-        self._test_field(fieldname)
         text = self._text_to_bytes(fieldname, text)
         for term, terminfo in self._terms.items_from(fieldname, text):
             if term[0] not in schema:
     def postings(self, fieldname, text, scorer=None):
         from whoosh.matching.wrappers import FilterMatcher
 
+        if self.is_closed:
+            raise ReaderClosed
         if fieldname not in self.schema:
             raise TermNotFound("No  field %r" % fieldname)
         text = self._text_to_bytes(fieldname, text)
         return matcher
 
     def vector(self, docnum, fieldname, format_=None):
+        if self.is_closed:
+            raise ReaderClosed
         if fieldname not in self.schema:
             raise TermNotFound("No  field %r" % fieldname)
         vformat = format_ or self.schema[fieldname].vector
     # Graph methods
 
     def has_word_graph(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         if fieldname not in self.schema:
             return False
         if not self.schema[fieldname].spelling:
         return gr.has_root(fieldname)
 
     def word_graph(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         if not self.has_word_graph(fieldname):
             raise KeyError("No word graph for field %r" % fieldname)
         gr = self._get_graph()
         return fst.Node(gr, gr.root(fieldname))
 
     def terms_within(self, fieldname, text, maxdist, prefix=0):
+        if self.is_closed:
+            raise ReaderClosed
         if not self.has_word_graph(fieldname):
             # This reader doesn't have a graph stored, use the slow method
             return IndexReader.terms_within(self, fieldname, text, maxdist,
     # Column methods
 
     def has_column(self, fieldname):
+        if self.is_closed:
+            raise ReaderClosed
         coltype = self.schema[fieldname].column_type
         return coltype and self._perdoc.has_column(fieldname)
 
     def column_reader(self, fieldname, column=None, translate=True):
+        if self.is_closed:
+            raise ReaderClosed
         fieldobj = self.schema[fieldname]
         if not self.has_column(fieldname):
             raise Exception("No column for field %r" % fieldname)

tests/test_results.py

 from whoosh.codec.whoosh3 import W3Codec
 from whoosh.compat import u, xrange, text_type, permutations
 from whoosh.filedb.filestore import RamStorage
+from whoosh.util.testing import TempStorage
 
 
 def test_score_retrieval():
         assert len(r) == 1
         hit = r[0]
         assert hit["text"] == u("alfa bravo charlie")
+
+
+def test_closed_searcher():
+    from whoosh.reading import ReaderClosed
+
+    schema = fields.Schema(key=fields.KEYWORD(stored=True, sortable=True,
+                                              spelling=True))
+
+    with TempStorage() as st:
+        ix = st.create_index(schema)
+        with ix.writer() as w:
+            w.add_document(key=u("alfa"))
+            w.add_document(key=u("bravo"))
+            w.add_document(key=u("charlie"))
+            w.add_document(key=u("delta"))
+            w.add_document(key=u("echo"))
+
+        s = ix.searcher()
+        r = s.search(query.TermRange("key", "b", "d"))
+        s.close()
+        assert s.is_closed
+        with pytest.raises(ReaderClosed):
+            assert r[0]["key"] == "bravo"
+        with pytest.raises(ReaderClosed):
+            s.reader().column_reader("key")
+        with pytest.raises(ReaderClosed):
+            s.reader().has_word_graph("key")
+        with pytest.raises(ReaderClosed):
+            s.suggest("key", "brovo")
+
+        s = ix.searcher()
+        r = s.search(query.TermRange("key", "b", "d"))
+        assert r[0]
+        assert r[0]["key"] == "bravo"
+        c = s.reader().column_reader("key")
+        assert c[1] == "bravo"
+        assert s.reader().has_word_graph("key")
+        assert s.suggest("key", "brovo") == ["bravo"]