Skip to main content

lb_tantivy/query/term_query/
term_query.rs

1use std::fmt;
2
3use super::term_weight::TermWeight;
4use crate::query::bm25::Bm25Weight;
5use crate::query::{EnableScoring, Explanation, Query, Weight};
6use crate::schema::IndexRecordOption;
7use crate::Term;
8
9/// A Term query matches all of the documents
10/// containing a specific term.
11///
12/// The score associated is defined as
13/// `idf` *  sqrt(`term_freq` / `field norm`)
14/// in which :
15/// * `idf`        - inverse document frequency.
16/// * `term_freq`  - number of occurrences of the term in the field
17/// * `field norm` - number of tokens in the field.
18///
19/// ```rust
20/// use tantivy::collector::{Count, TopDocs};
21/// use tantivy::query::TermQuery;
22/// use tantivy::schema::{Schema, TEXT, IndexRecordOption};
23/// use tantivy::{doc, Index, IndexWriter, Term};
24/// # fn test() -> tantivy::Result<()> {
25/// let mut schema_builder = Schema::builder();
26/// let title = schema_builder.add_text_field("title", TEXT);
27/// let schema = schema_builder.build();
28/// let index = Index::create_in_ram(schema);
29/// {
30///     let mut index_writer: IndexWriter = index.writer(15_000_000)?;
31///     index_writer.add_document(doc!(
32///         title => "The Name of the Wind",
33///     ))?;
34///     index_writer.add_document(doc!(
35///         title => "The Diary of Muadib",
36///     ))?;
37///     index_writer.add_document(doc!(
38///         title => "A Dairy Cow",
39///     ))?;
40///     index_writer.add_document(doc!(
41///         title => "The Diary of a Young Girl",
42///     ))?;
43///     index_writer.commit()?;
44/// }
45/// let reader = index.reader()?;
46/// let searcher = reader.searcher();
47/// let query = TermQuery::new(
48///     Term::from_field_text(title, "diary"),
49///     IndexRecordOption::Basic,
50/// );
51/// let (top_docs, count) = searcher.search(&query, &(TopDocs::with_limit(2), Count))?;
52/// assert_eq!(count, 2);
53/// Ok(())
54/// # }
55/// # assert!(test().is_ok());
56/// ```
57#[derive(Clone)]
58pub struct TermQuery {
59    term: Term,
60    index_record_option: IndexRecordOption,
61}
62
63impl fmt::Debug for TermQuery {
64    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
65        write!(f, "TermQuery({:?})", self.term)
66    }
67}
68
69impl TermQuery {
70    /// Creates a new term query.
71    pub fn new(term: Term, segment_postings_options: IndexRecordOption) -> TermQuery {
72        TermQuery {
73            term,
74            index_record_option: segment_postings_options,
75        }
76    }
77
78    /// The `Term` this query is built out of.
79    pub fn term(&self) -> &Term {
80        &self.term
81    }
82
83    /// Returns a weight object.
84    ///
85    /// While `.weight(...)` returns a boxed trait object,
86    /// this method return a specific implementation.
87    /// This is useful for optimization purpose.
88    pub fn specialized_weight(
89        &self,
90        enable_scoring: EnableScoring<'_>,
91    ) -> crate::Result<TermWeight> {
92        let schema = enable_scoring.schema();
93        let field_entry = schema.get_field_entry(self.term.field());
94        if !field_entry.is_indexed() {
95            let error_msg = format!("Field {:?} is not indexed.", field_entry.name());
96            return Err(crate::TantivyError::SchemaError(error_msg));
97        }
98        let bm25_weight = match enable_scoring {
99            EnableScoring::Enabled {
100                statistics_provider,
101                ..
102            } => Bm25Weight::for_terms(statistics_provider, &[self.term.clone()])?,
103            EnableScoring::Disabled { .. } => {
104                Bm25Weight::new(Explanation::new("<no score>", 1.0f32), 1.0f32)
105            }
106        };
107        let scoring_enabled = enable_scoring.is_scoring_enabled();
108        let index_record_option = if scoring_enabled {
109            self.index_record_option
110        } else {
111            IndexRecordOption::Basic
112        };
113
114        Ok(TermWeight::new(
115            self.term.clone(),
116            index_record_option,
117            bm25_weight,
118            scoring_enabled,
119        ))
120    }
121}
122
123impl Query for TermQuery {
124    fn weight(&self, enable_scoring: EnableScoring<'_>) -> crate::Result<Box<dyn Weight>> {
125        Ok(Box::new(self.specialized_weight(enable_scoring)?))
126    }
127    fn query_terms<'a>(&'a self, visitor: &mut dyn FnMut(&'a Term, bool)) {
128        visitor(&self.term, false);
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use std::net::{IpAddr, Ipv6Addr};
135    use std::str::FromStr;
136
137    use columnar::MonotonicallyMappableToU128;
138
139    use crate::collector::{Count, TopDocs};
140    use crate::query::{Query, QueryParser, TermQuery};
141    use crate::schema::{IndexRecordOption, IntoIpv6Addr, Schema, INDEXED, STORED};
142    use crate::{Index, IndexWriter, Term};
143
144    #[test]
145    fn search_ip_test() {
146        let mut schema_builder = Schema::builder();
147        let ip_field = schema_builder.add_ip_addr_field("ip", INDEXED | STORED);
148        let schema = schema_builder.build();
149        let index = Index::create_in_ram(schema);
150        let ip_addr_1 = IpAddr::from_str("127.0.0.1").unwrap().into_ipv6_addr();
151        let ip_addr_2 = Ipv6Addr::from_u128(10);
152
153        {
154            let mut index_writer: IndexWriter = index.writer_for_tests().unwrap();
155            index_writer
156                .add_document(doc!(
157                    ip_field => ip_addr_1
158                ))
159                .unwrap();
160            index_writer
161                .add_document(doc!(
162                    ip_field => ip_addr_2
163                ))
164                .unwrap();
165
166            index_writer.commit().unwrap();
167        }
168        let reader = index.reader().unwrap();
169        let searcher = reader.searcher();
170
171        let assert_single_hit = |query| {
172            let (_top_docs, count) = searcher
173                .search(&query, &(TopDocs::with_limit(2), Count))
174                .unwrap();
175            assert_eq!(count, 1);
176        };
177        let query_from_text = |text: String| {
178            QueryParser::for_index(&index, vec![ip_field])
179                .parse_query(&text)
180                .unwrap()
181        };
182
183        let query_from_ip = |ip_addr| -> Box<dyn Query> {
184            Box::new(TermQuery::new(
185                Term::from_field_ip_addr(ip_field, ip_addr),
186                IndexRecordOption::Basic,
187            ))
188        };
189
190        assert_single_hit(query_from_ip(ip_addr_1));
191        assert_single_hit(query_from_ip(ip_addr_2));
192        assert_single_hit(query_from_text("127.0.0.1".to_string()));
193        assert_single_hit(query_from_text("\"127.0.0.1\"".to_string()));
194        assert_single_hit(query_from_text(format!("\"{ip_addr_1}\"")));
195        assert_single_hit(query_from_text(format!("\"{ip_addr_2}\"")));
196    }
197}