wrappers.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # Copyright 2007 Matt Chaput. All rights reserved.
  2. #
  3. # Redistribution and use in source and binary forms, with or without
  4. # modification, are permitted provided that the following conditions are met:
  5. #
  6. # 1. Redistributions of source code must retain the above copyright notice,
  7. # this list of conditions and the following disclaimer.
  8. #
  9. # 2. Redistributions in binary form must reproduce the above copyright
  10. # notice, this list of conditions and the following disclaimer in the
  11. # documentation and/or other materials provided with the distribution.
  12. #
  13. # THIS SOFTWARE IS PROVIDED BY MATT CHAPUT ``AS IS'' AND ANY EXPRESS OR
  14. # IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  15. # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  16. # EVENT SHALL MATT CHAPUT OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  17. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  18. # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
  19. # OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
  20. # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  21. # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
  22. # EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  23. #
  24. # The views and conclusions contained in the software and documentation are
  25. # those of the authors and should not be interpreted as representing official
  26. # policies, either expressed or implied, of Matt Chaput.
  27. from __future__ import division
  28. from array import array
  29. from whoosh import matching
  30. from whoosh.compat import text_type, u, xrange
  31. from whoosh.query import qcore
  32. class WrappingQuery(qcore.Query):
  33. def __init__(self, child):
  34. self.child = child
  35. def __repr__(self):
  36. return "%s(%r)" % (self.__class__.__name__, self.child)
  37. def __hash__(self):
  38. return hash(self.__class__.__name__) ^ hash(self.child)
  39. def _rewrap(self, child):
  40. return self.__class__(child)
  41. def is_leaf(self):
  42. return False
  43. def children(self):
  44. yield self.child
  45. def apply(self, fn):
  46. return self._rewrap(fn(self.child))
  47. def requires(self):
  48. return self.child.requires()
  49. def field(self):
  50. return self.child.field()
  51. def with_boost(self, boost):
  52. return self._rewrap(self.child.with_boost(boost))
  53. def estimate_size(self, ixreader):
  54. return self.child.estimate_size(ixreader)
  55. def estimate_min_size(self, ixreader):
  56. return self.child.estimate_min_size(ixreader)
  57. def matcher(self, searcher, context=None):
  58. return self.child.matcher(searcher, context)
  59. class Not(qcore.Query):
  60. """Excludes any documents that match the subquery.
  61. >>> # Match documents that contain 'render' but not 'texture'
  62. >>> And([Term("content", u"render"),
  63. ... Not(Term("content", u"texture"))])
  64. >>> # You can also do this
  65. >>> Term("content", u"render") - Term("content", u"texture")
  66. """
  67. __inittypes__ = dict(query=qcore.Query)
  68. def __init__(self, query, boost=1.0):
  69. """
  70. :param query: A :class:`Query` object. The results of this query
  71. are *excluded* from the parent query.
  72. :param boost: Boost is meaningless for excluded documents but this
  73. keyword argument is accepted for the sake of a consistent
  74. interface.
  75. """
  76. self.query = query
  77. self.boost = boost
  78. def __eq__(self, other):
  79. return other and self.__class__ is other.__class__ and\
  80. self.query == other.query
  81. def __repr__(self):
  82. return "%s(%s)" % (self.__class__.__name__, repr(self.query))
  83. def __unicode__(self):
  84. return u("NOT ") + text_type(self.query)
  85. __str__ = __unicode__
  86. def __hash__(self):
  87. return (hash(self.__class__.__name__)
  88. ^ hash(self.query)
  89. ^ hash(self.boost))
  90. def is_leaf(self):
  91. return False
  92. def children(self):
  93. yield self.query
  94. def apply(self, fn):
  95. return self.__class__(fn(self.query))
  96. def normalize(self):
  97. q = self.query.normalize()
  98. if q is qcore.NullQuery:
  99. return q
  100. else:
  101. return self.__class__(q, boost=self.boost)
  102. def field(self):
  103. return None
  104. def estimate_size(self, ixreader):
  105. return ixreader.doc_count()
  106. def estimate_min_size(self, ixreader):
  107. return 1 if ixreader.doc_count() else 0
  108. def matcher(self, searcher, context=None):
  109. # Usually only called if Not is the root query. Otherwise, queries such
  110. # as And and Or do special handling of Not subqueries.
  111. reader = searcher.reader()
  112. child = self.query.matcher(searcher, searcher.boolean_context())
  113. return matching.InverseMatcher(child, reader.doc_count_all(),
  114. missing=reader.is_deleted)
  115. class ConstantScoreQuery(WrappingQuery):
  116. """Wraps a query and uses a matcher that always gives a constant score
  117. to all matching documents. This is a useful optimization when you don't
  118. care about scores from a certain branch of the query tree because it is
  119. simply acting as a filter. See also the :class:`AndMaybe` query.
  120. """
  121. def __init__(self, child, score=1.0):
  122. WrappingQuery.__init__(self, child)
  123. self.score = score
  124. def __eq__(self, other):
  125. return (other and self.__class__ is other.__class__
  126. and self.child == other.child and self.score == other.score)
  127. def __hash__(self):
  128. return hash(self.child) ^ hash(self.score)
  129. def _rewrap(self, child):
  130. return self.__class__(child, self.score)
  131. def matcher(self, searcher, context=None):
  132. from whoosh.searching import SearchContext
  133. context = context or SearchContext()
  134. m = self.child.matcher(searcher, context)
  135. if context.needs_current or isinstance(m, matching.NullMatcherClass):
  136. return m
  137. else:
  138. ids = array("I", m.all_ids())
  139. return matching.ListMatcher(ids, all_weights=self.score,
  140. term=m.term())
  141. class WeightingQuery(WrappingQuery):
  142. """Wraps a query and uses a specific :class:`whoosh.sorting.WeightingModel`
  143. to score documents that match the wrapped query.
  144. """
  145. def __init__(self, child, weighting):
  146. WrappingQuery.__init__(self, child)
  147. self.weighting = weighting
  148. def matcher(self, searcher, context=None):
  149. # Replace the passed-in weighting with the one configured on this query
  150. context.set(weighting=self.weighting)
  151. return self.child.matcher(searcher, context)