tantivy/query/term_query/
term_query.rs1use std::fmt;
2use std::ops::Bound;
3
4use super::term_weight::TermWeight;
5use crate::query::bm25::Bm25Weight;
6use crate::query::range_query::is_type_valid_for_fastfield_range_query;
7use crate::query::{EnableScoring, Explanation, Query, RangeQuery, Weight};
8use crate::schema::IndexRecordOption;
9use crate::Term;
10
11#[derive(Clone)]
60pub struct TermQuery {
61 term: Term,
62 index_record_option: IndexRecordOption,
63}
64
65impl fmt::Debug for TermQuery {
66 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
67 write!(f, "TermQuery({:?})", self.term)
68 }
69}
70
71impl TermQuery {
72 pub fn new(term: Term, segment_postings_options: IndexRecordOption) -> TermQuery {
74 TermQuery {
75 term,
76 index_record_option: segment_postings_options,
77 }
78 }
79
80 pub fn term(&self) -> &Term {
82 &self.term
83 }
84
85 pub fn specialized_weight(
91 &self,
92 enable_scoring: EnableScoring<'_>,
93 ) -> crate::Result<TermWeight> {
94 let schema = enable_scoring.schema();
95 let field_entry = schema.get_field_entry(self.term.field());
96 if !field_entry.is_indexed() {
97 let error_msg = format!("Field {:?} is not indexed.", field_entry.name());
98 return Err(crate::TantivyError::SchemaError(error_msg));
99 }
100 let bm25_weight = match enable_scoring {
101 EnableScoring::Enabled {
102 statistics_provider,
103 ..
104 } => Bm25Weight::for_terms(statistics_provider, std::slice::from_ref(&self.term))?,
105 EnableScoring::Disabled { .. } => {
106 Bm25Weight::new(Explanation::new("<no score>", 1.0f32), 1.0f32)
107 }
108 };
109 let scoring_enabled = enable_scoring.is_scoring_enabled();
110 let index_record_option = if scoring_enabled {
111 self.index_record_option
112 } else {
113 IndexRecordOption::Basic
114 };
115
116 Ok(TermWeight::new(
117 self.term.clone(),
118 index_record_option,
119 bm25_weight,
120 scoring_enabled,
121 ))
122 }
123}
124
125impl Query for TermQuery {
126 fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>> {
127 let schema = enable_scoring.schema();
133 let field_entry = schema.get_field_entry(self.term.field());
134 if !field_entry.is_indexed()
135 && field_entry.is_fast()
136 && is_type_valid_for_fastfield_range_query(self.term.typ())
137 && !enable_scoring.is_scoring_enabled()
138 {
139 let range_query = RangeQuery::new(
140 Bound::Included(self.term.clone()),
141 Bound::Included(self.term.clone()),
142 );
143 return range_query.weight(enable_scoring);
144 }
145 Ok(Box::new(self.specialized_weight(enable_scoring)?))
146 }
147 fn query_terms<'a>(&'a self, visitor: &mut dyn FnMut(&'a Term, bool)) {
148 visitor(&self.term, false);
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use std::net::{IpAddr, Ipv6Addr};
155 use std::str::FromStr;
156
157 use columnar::MonotonicallyMappableToU128;
158
159 use crate::collector::{Count, TopDocs};
160 use crate::query::{Query, QueryParser, TermQuery};
161 use crate::schema::{IndexRecordOption, IntoIpv6Addr, Schema, INDEXED, STORED};
162 use crate::{Index, IndexWriter, Term};
163
164 #[test]
165 fn search_ip_test() {
166 let mut schema_builder = Schema::builder();
167 let ip_field = schema_builder.add_ip_addr_field("ip", INDEXED | STORED);
168 let schema = schema_builder.build();
169 let index = Index::create_in_ram(schema);
170 let ip_addr_1 = IpAddr::from_str("127.0.0.1").unwrap().into_ipv6_addr();
171 let ip_addr_2 = Ipv6Addr::from_u128(10);
172
173 {
174 let mut index_writer: IndexWriter = index.writer_for_tests().unwrap();
175 index_writer
176 .add_document(doc!(
177 ip_field => ip_addr_1
178 ))
179 .unwrap();
180 index_writer
181 .add_document(doc!(
182 ip_field => ip_addr_2
183 ))
184 .unwrap();
185
186 index_writer.commit().unwrap();
187 }
188 let reader = index.reader().unwrap();
189 let searcher = reader.searcher();
190
191 let assert_single_hit = |query| {
192 let (_top_docs, count) = searcher
193 .search(&query, &(TopDocs::with_limit(2).order_by_score(), Count))
194 .unwrap();
195 assert_eq!(count, 1);
196 };
197 let query_from_text = |text: String| {
198 QueryParser::for_index(&index, vec![ip_field])
199 .parse_query(&text)
200 .unwrap()
201 };
202
203 let query_from_ip = |ip_addr| -> Box<dyn Query> {
204 Box::new(TermQuery::new(
205 Term::from_field_ip_addr(ip_field, ip_addr),
206 IndexRecordOption::Basic,
207 ))
208 };
209
210 assert_single_hit(query_from_ip(ip_addr_1));
211 assert_single_hit(query_from_ip(ip_addr_2));
212 assert_single_hit(query_from_text("127.0.0.1".to_string()));
213 assert_single_hit(query_from_text("\"127.0.0.1\"".to_string()));
214 assert_single_hit(query_from_text(format!("\"{ip_addr_1}\"")));
215 assert_single_hit(query_from_text(format!("\"{ip_addr_2}\"")));
216 }
217}