mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-10-28 06:26:31 +00:00
Added documentation
This commit is contained in:
@@ -5,6 +5,9 @@ use std::io;
|
||||
use ScoredDoc;
|
||||
|
||||
|
||||
/// Collector that does nothing.
|
||||
/// This is used in the chain Collector and will hopefully
|
||||
/// be optimized away by the compiler.
|
||||
pub struct DoNothingCollector;
|
||||
impl Collector for DoNothingCollector {
|
||||
#[inline]
|
||||
@@ -15,24 +18,20 @@ impl Collector for DoNothingCollector {
|
||||
fn collect(&mut self, _: ScoredDoc) {}
|
||||
}
|
||||
|
||||
/// Zero-cost abstraction used to collect on multiple collectors.
|
||||
/// This contraption is only usable if the type of your collectors
|
||||
/// are known at compile time.
|
||||
pub struct ChainedCollector<Left: Collector, Right: Collector> {
|
||||
left: Left,
|
||||
right: Right
|
||||
}
|
||||
|
||||
impl<Left: Collector, Right: Collector> ChainedCollector<Left, Right> {
|
||||
|
||||
pub fn start() -> ChainedCollector<DoNothingCollector, DoNothingCollector> {
|
||||
ChainedCollector {
|
||||
left: DoNothingCollector,
|
||||
right: DoNothingCollector
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<C: Collector>(self, new_collector: &mut C) -> ChainedCollector<Self, MutRefCollector<C>> {
|
||||
/// Adds a collector
|
||||
pub fn push<C: Collector>(self, new_collector: &mut C) -> ChainedCollector<Self, &mut C> {
|
||||
ChainedCollector {
|
||||
left: self,
|
||||
right: MutRefCollector(new_collector),
|
||||
right: new_collector,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,6 +49,7 @@ impl<Left: Collector, Right: Collector> Collector for ChainedCollector<Left, Rig
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a `ChainedCollector`
|
||||
pub fn chain() -> ChainedCollector<DoNothingCollector, DoNothingCollector> {
|
||||
ChainedCollector {
|
||||
left: DoNothingCollector,
|
||||
@@ -57,17 +57,6 @@ pub fn chain() -> ChainedCollector<DoNothingCollector, DoNothingCollector> {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MutRefCollector<'a, C: Collector + 'a>(&'a mut C);
|
||||
|
||||
impl<'a, C: Collector> Collector for MutRefCollector<'a, C> {
|
||||
fn set_segment(&mut self, segment_local_id: SegmentLocalId, segment: &SegmentReader) -> io::Result<()> {
|
||||
self.0.set_segment(segment_local_id, segment)
|
||||
}
|
||||
|
||||
fn collect(&mut self, scored_doc: ScoredDoc) {
|
||||
self.0.collect(scored_doc)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
use SegmentReader;
|
||||
use SegmentLocalId;
|
||||
use std::io;
|
||||
use ScoredDoc;
|
||||
|
||||
pub trait Collector {
|
||||
fn set_segment(&mut self, segment_local_id: SegmentLocalId, segment: &SegmentReader) -> io::Result<()>;
|
||||
fn collect(&mut self, scored_doc: ScoredDoc);
|
||||
}
|
||||
@@ -4,15 +4,15 @@ use ScoredDoc;
|
||||
use SegmentReader;
|
||||
use SegmentLocalId;
|
||||
|
||||
// `CountCollector` collector only counts how many
|
||||
// document are matching the query.
|
||||
/// `CountCollector` collector only counts how many
|
||||
/// document are matching the query.
|
||||
pub struct CountCollector {
|
||||
count: usize,
|
||||
}
|
||||
|
||||
impl CountCollector {
|
||||
// Returns the count of document that where
|
||||
// collected.
|
||||
/// Returns the count of document that where
|
||||
/// collected.
|
||||
pub fn count(&self,) -> usize {
|
||||
self.count
|
||||
}
|
||||
|
||||
+128
-71
@@ -1,15 +1,8 @@
|
||||
|
||||
use DocId;
|
||||
use SegmentReader;
|
||||
use SegmentLocalId;
|
||||
use fastfield::U32FastFieldReader;
|
||||
use schema::Field;
|
||||
use ScoredDoc;
|
||||
use std::io;
|
||||
|
||||
mod collector;
|
||||
pub use self::collector::Collector;
|
||||
|
||||
mod count_collector;
|
||||
pub use self::count_collector::CountCollector;
|
||||
|
||||
@@ -22,83 +15,147 @@ pub use self::top_collector::TopCollector;
|
||||
mod chained_collector;
|
||||
pub use self::chained_collector::chain;
|
||||
|
||||
pub struct TestCollector {
|
||||
offset: DocId,
|
||||
segment_max_doc: DocId,
|
||||
docs: Vec<DocId>,
|
||||
/// Collectors are in charge of collecting and retaining relevant
|
||||
/// information from the document found and scored by the query.
|
||||
///
|
||||
///
|
||||
/// For instance,
|
||||
/// - keeping track of the top 10 best documents
|
||||
/// - computing a break down over a fast field
|
||||
/// - computing the number of documents matching the query
|
||||
///
|
||||
///
|
||||
/// Queries are in charge of pushing the `DocSet` to the collector.
|
||||
///
|
||||
/// As they work on multiple segment, they first inform
|
||||
/// the collector of a change in segment and then
|
||||
/// call the collect method to push document to the collector.
|
||||
///
|
||||
/// Temporally, our collector will receive calls
|
||||
/// - `.set_segment(0, segment_reader_0)`
|
||||
/// - `.collect(doc0_of_segment_0)`
|
||||
/// - `.collect(...)`
|
||||
/// - `.collect(last_doc_of_segment_0)`
|
||||
/// - `.set_segment(1, segment_reader_1)`
|
||||
/// - `.collect(doc0_of_segment_1)`
|
||||
/// - `.collect(...)`
|
||||
/// - `.collect(last_doc_of_segment_1)`
|
||||
/// - `...`
|
||||
/// - `.collect(last_doc_of_last_segment)`
|
||||
///
|
||||
/// Segments are not guaranteed to be visited in any specific order.
|
||||
pub trait Collector {
|
||||
/// `set_segment` is called before starting enumerating
|
||||
/// on this segment.
|
||||
fn set_segment(&mut self, segment_local_id: SegmentLocalId, segment: &SegmentReader) -> io::Result<()>;
|
||||
/// The query pushes scored document to the collector via this method.
|
||||
fn collect(&mut self, scored_doc: ScoredDoc);
|
||||
}
|
||||
|
||||
impl TestCollector {
|
||||
pub fn docs(self,) -> Vec<DocId> {
|
||||
self.docs
|
||||
|
||||
impl<'a, C: Collector> Collector for &'a mut C {
|
||||
fn set_segment(&mut self, segment_local_id: SegmentLocalId, segment: &SegmentReader) -> io::Result<()> {
|
||||
(*self).set_segment(segment_local_id, segment)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestCollector {
|
||||
fn default() -> TestCollector {
|
||||
TestCollector {
|
||||
docs: Vec::new(),
|
||||
offset: 0,
|
||||
segment_max_doc: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector for TestCollector {
|
||||
|
||||
fn set_segment(&mut self, _: SegmentLocalId, reader: &SegmentReader) -> io::Result<()> {
|
||||
self.offset += self.segment_max_doc;
|
||||
self.segment_max_doc = reader.max_doc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// The query pushes scored document to the collector via this method.
|
||||
fn collect(&mut self, scored_doc: ScoredDoc) {
|
||||
self.docs.push(scored_doc.doc() + self.offset);
|
||||
(*self).collect(scored_doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub struct FastFieldTestCollector {
|
||||
vals: Vec<u32>,
|
||||
field: Field,
|
||||
ff_reader: Option<U32FastFieldReader>,
|
||||
}
|
||||
|
||||
impl FastFieldTestCollector {
|
||||
pub fn for_field(field: Field) -> FastFieldTestCollector {
|
||||
FastFieldTestCollector {
|
||||
vals: Vec::new(),
|
||||
field: field,
|
||||
ff_reader: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vals(&self,) -> &Vec<u32> {
|
||||
&self.vals
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector for FastFieldTestCollector {
|
||||
|
||||
fn set_segment(&mut self, _: SegmentLocalId, reader: &SegmentReader) -> io::Result<()> {
|
||||
self.ff_reader = Some(try!(reader.get_fast_field_reader(self.field)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect(&mut self, scored_doc: ScoredDoc) {
|
||||
let val = self.ff_reader.as_ref().unwrap().get(scored_doc.doc());
|
||||
self.vals.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
pub mod tests {
|
||||
|
||||
use super::*;
|
||||
use test::Bencher;
|
||||
use ScoredDoc;
|
||||
use DocId;
|
||||
use core::SegmentReader;
|
||||
use std::io;
|
||||
use SegmentLocalId;
|
||||
use fastfield::U32FastFieldReader;
|
||||
use schema::Field;
|
||||
|
||||
/// Stores all of the doc ids.
|
||||
/// This collector is only used for tests.
|
||||
/// It is unusable in practise, as it does not store
|
||||
/// the segment ordinals
|
||||
pub struct TestCollector {
|
||||
offset: DocId,
|
||||
segment_max_doc: DocId,
|
||||
docs: Vec<DocId>,
|
||||
}
|
||||
|
||||
impl TestCollector {
|
||||
/// Return the exhalist of documents.
|
||||
pub fn docs(self,) -> Vec<DocId> {
|
||||
self.docs
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TestCollector {
|
||||
fn default() -> TestCollector {
|
||||
TestCollector {
|
||||
docs: Vec::new(),
|
||||
offset: 0,
|
||||
segment_max_doc: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector for TestCollector {
|
||||
|
||||
fn set_segment(&mut self, _: SegmentLocalId, reader: &SegmentReader) -> io::Result<()> {
|
||||
self.offset += self.segment_max_doc;
|
||||
self.segment_max_doc = reader.max_doc();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect(&mut self, scored_doc: ScoredDoc) {
|
||||
self.docs.push(scored_doc.doc() + self.offset);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
/// Collects in order all of the fast field for all of the
|
||||
/// doc of the `DocSet`
|
||||
///
|
||||
/// This collector is essentially useful for tests.
|
||||
pub struct FastFieldTestCollector {
|
||||
vals: Vec<u32>,
|
||||
field: Field,
|
||||
ff_reader: Option<U32FastFieldReader>,
|
||||
}
|
||||
|
||||
impl FastFieldTestCollector {
|
||||
pub fn for_field(field: Field) -> FastFieldTestCollector {
|
||||
FastFieldTestCollector {
|
||||
vals: Vec::new(),
|
||||
field: field,
|
||||
ff_reader: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vals(&self,) -> &Vec<u32> {
|
||||
&self.vals
|
||||
}
|
||||
}
|
||||
|
||||
impl Collector for FastFieldTestCollector {
|
||||
fn set_segment(&mut self, _: SegmentLocalId, reader: &SegmentReader) -> io::Result<()> {
|
||||
self.ff_reader = Some(try!(reader.get_fast_field_reader(self.field)));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn collect(&mut self, scored_doc: ScoredDoc) {
|
||||
let val = self.ff_reader.as_ref().unwrap().get(scored_doc.doc());
|
||||
self.vals.push(val);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[bench]
|
||||
fn build_collector(b: &mut Bencher) {
|
||||
|
||||
@@ -4,11 +4,17 @@ use ScoredDoc;
|
||||
use SegmentReader;
|
||||
use SegmentLocalId;
|
||||
|
||||
|
||||
/// Multicollector makes it possible to collect on more than one collector
|
||||
/// It should only be used for use cases where the Collector types is unknown
|
||||
/// at compile time.
|
||||
/// If the type of the collectors is known, you should prefer to use `ChainedCollector`.
|
||||
pub struct MultiCollector<'a> {
|
||||
collectors: Vec<&'a mut Collector>,
|
||||
}
|
||||
|
||||
impl<'a> MultiCollector<'a> {
|
||||
/// Constructor
|
||||
pub fn from(collectors: Vec<&'a mut Collector>) -> MultiCollector {
|
||||
MultiCollector {
|
||||
collectors: collectors,
|
||||
@@ -16,8 +22,8 @@ impl<'a> MultiCollector<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Collector for MultiCollector<'a> {
|
||||
|
||||
impl<'a> Collector for MultiCollector<'a> {
|
||||
fn set_segment(&mut self, segment_local_id: SegmentLocalId, segment: &SegmentReader) -> io::Result<()> {
|
||||
for collector in &mut self.collectors {
|
||||
try!(collector.set_segment(segment_local_id, segment));
|
||||
|
||||
@@ -36,6 +36,12 @@ impl PartialEq for GlobalScoredDoc {
|
||||
|
||||
impl Eq for GlobalScoredDoc {}
|
||||
|
||||
|
||||
/// The Top Collector keeps track of the K documents
|
||||
/// with the best scores.
|
||||
///
|
||||
/// The implementation is based on a `BinaryHeap`.
|
||||
/// The theorical complexity is `O(n log K)`.
|
||||
pub struct TopCollector {
|
||||
limit: usize,
|
||||
heap: BinaryHeap<GlobalScoredDoc>,
|
||||
@@ -58,7 +64,11 @@ impl TopCollector {
|
||||
segment_id: 0,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Returns the decreasingly sorted K-best documents.
|
||||
///
|
||||
/// Calling this method will triggers the sort.
|
||||
/// The result of the sort is not cached.
|
||||
pub fn docs(&self) -> Vec<DocAddress> {
|
||||
self.score_docs()
|
||||
.into_iter()
|
||||
@@ -66,6 +76,10 @@ impl TopCollector {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the decreasingly sorted K-best ScoredDocument.
|
||||
///
|
||||
/// Calling this method will triggers the sort.
|
||||
/// The result of the sort is not cached.
|
||||
pub fn score_docs(&self) -> Vec<(Score, DocAddress)> {
|
||||
let mut scored_docs: Vec<GlobalScoredDoc> = self.heap
|
||||
.iter()
|
||||
@@ -76,7 +90,9 @@ impl TopCollector {
|
||||
.map(|GlobalScoredDoc(score, doc_address)| (score, doc_address))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
||||
/// Return true iff at least K document have gone through
|
||||
/// the collector.
|
||||
#[inline]
|
||||
pub fn at_capacity(&self, ) -> bool {
|
||||
self.heap.len() >= self.limit
|
||||
|
||||
@@ -140,7 +140,7 @@ impl SegmentReader {
|
||||
// is it because the term is not here, or because the
|
||||
// field does not handle this functionality.
|
||||
pub fn read_postings(&self, term: &Term, option: SegmentPostingsOption) -> Option<SegmentPostings> {
|
||||
let field = term.get_field();
|
||||
let field = term.field();
|
||||
let field_entry = self.schema.get_field_entry(field);
|
||||
let term_info = get!(self.get_term_info(&term));
|
||||
let offset = term_info.postings_offset as usize;
|
||||
@@ -183,7 +183,7 @@ impl SegmentReader {
|
||||
}
|
||||
|
||||
pub fn read_postings_all_info(&self, term: &Term) -> SegmentPostings {
|
||||
let field_entry = self.schema.get_field_entry(term.get_field());
|
||||
let field_entry = self.schema.get_field_entry(term.field());
|
||||
let segment_posting_option = match *field_entry.field_type() {
|
||||
FieldType::Str(ref text_options) => {
|
||||
match text_options.get_indexing_options() {
|
||||
|
||||
@@ -274,8 +274,8 @@ mod tests {
|
||||
use schema::Term;
|
||||
use core::Index;
|
||||
use DocAddress;
|
||||
use collector::FastFieldTestCollector;
|
||||
use collector::TestCollector;
|
||||
use collector::tests::FastFieldTestCollector;
|
||||
use collector::tests::TestCollector;
|
||||
use query::MultiTermQuery;
|
||||
use schema::TextIndexingOptions;
|
||||
|
||||
|
||||
@@ -107,7 +107,7 @@ impl<'a> SegmentWriter<'a> {
|
||||
|
||||
pub fn add_document(&mut self, doc: &Document, schema: &Schema) -> io::Result<()> {
|
||||
let doc_id = self.max_doc;
|
||||
for (field, field_values) in doc.get_sorted_fields() {
|
||||
for (field, field_values) in doc.get_sorted_field_values() {
|
||||
let field_posting_writer: &mut Box<PostingsWriter> = &mut self.per_field_postings_writers[field.0 as usize];
|
||||
let field_options = schema.get_field_entry(field);
|
||||
match *field_options.field_type() {
|
||||
@@ -144,7 +144,7 @@ impl<'a> SegmentWriter<'a> {
|
||||
|
||||
self.fast_field_writers.add_document(doc);
|
||||
let stored_fieldvalues: Vec<&FieldValue> = doc
|
||||
.get_fields()
|
||||
.field_values()
|
||||
.iter()
|
||||
.filter(|field_value| schema.get_field_entry(field_value.field()).is_stored())
|
||||
.collect();
|
||||
|
||||
+1
-1
@@ -143,7 +143,7 @@ impl ScoredDoc {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use collector::TestCollector;
|
||||
use collector::tests::TestCollector;
|
||||
use query::MultiTermQuery;
|
||||
use Index;
|
||||
use core::SegmentReader;
|
||||
|
||||
@@ -82,7 +82,7 @@ impl PostingsSerializer {
|
||||
panic!("Called new_term, while the previous term was not closed.");
|
||||
}
|
||||
self.term_open = true;
|
||||
self.load_indexing_options(term.get_field());
|
||||
self.load_indexing_options(term.field());
|
||||
self.doc_ids.clear();
|
||||
self.last_doc_id_encoded = 0;
|
||||
self.term_freqs.clear();
|
||||
|
||||
@@ -74,7 +74,7 @@ impl MultiTermQuery {
|
||||
for &(occur, ref term) in &self.occur_terms {
|
||||
let _decode_one_timer = decode_timer.open("decode_one");
|
||||
if let Some(postings) = reader.read_postings(term, SegmentPostingsOption::Freq) {
|
||||
let field = term.get_field();
|
||||
let field = term.field();
|
||||
let fieldnorm_reader = try!(reader.get_fieldnorms_reader(field));
|
||||
postings_and_fieldnorms.push((occur, postings, fieldnorm_reader));
|
||||
}
|
||||
|
||||
+18
-17
@@ -4,12 +4,11 @@ use itertools::Itertools;
|
||||
/// Tantivy's Document is the object that can
|
||||
/// be indexed and then searched for.
|
||||
///
|
||||
/// Documents are really fundamentally a collection of unordered couple `(field, value)`.
|
||||
/// Documents are fundamentally a collection of unordered couple `(field, value)`.
|
||||
/// In this list, one field may appear more than once.
|
||||
///
|
||||
///
|
||||
|
||||
|
||||
/// Documents are really just a list of couple `(field, value)`.
|
||||
/// In this list, one field may appear more than once.
|
||||
#[derive(Debug, RustcEncodable, RustcDecodable)]
|
||||
@@ -44,30 +43,31 @@ impl Document {
|
||||
|
||||
/// Add a text field.
|
||||
pub fn add_text(&mut self, field: Field, text: &str) {
|
||||
self.add(FieldValue {
|
||||
field: field,
|
||||
value: Value::Str(String::from(text)),
|
||||
});
|
||||
let value = Value::Str(String::from(text));
|
||||
self.add(FieldValue::new(field, value));
|
||||
}
|
||||
|
||||
/// Add a u32 field
|
||||
pub fn add_u32(&mut self, field: Field, value: u32) {
|
||||
self.add(FieldValue {
|
||||
field: field,
|
||||
value: Value::U32(value),
|
||||
});
|
||||
self.add(FieldValue::new(field, Value::U32(value)));
|
||||
}
|
||||
|
||||
/// Add a field value
|
||||
pub fn add(&mut self, field_value: FieldValue) {
|
||||
self.field_values.push(field_value);
|
||||
}
|
||||
|
||||
pub fn get_fields(&self) -> &Vec<FieldValue> {
|
||||
|
||||
/// field_values accessor
|
||||
pub fn field_values(&self) -> &[FieldValue] {
|
||||
&self.field_values
|
||||
}
|
||||
|
||||
pub fn get_sorted_fields(&self) -> Vec<(Field, Vec<&FieldValue>)> {
|
||||
let mut field_values: Vec<&FieldValue> = self.get_fields().iter().collect();
|
||||
/// Sort and groups the field_values by field.
|
||||
///
|
||||
/// The result of this method is not cached and is
|
||||
/// computed on the fly when this method is called.
|
||||
pub fn get_sorted_field_values(&self) -> Vec<(Field, Vec<&FieldValue>)> {
|
||||
let mut field_values: Vec<&FieldValue> = self.field_values().iter().collect();
|
||||
field_values.sort_by_key(|field_value| field_value.field());
|
||||
field_values
|
||||
.into_iter()
|
||||
@@ -79,6 +79,7 @@ impl Document {
|
||||
.collect::<Vec<(Field, Vec<&FieldValue>)>>()
|
||||
}
|
||||
|
||||
/// Returns all of the `FieldValue`s associated the given field
|
||||
pub fn get_all(&self, field: Field) -> Vec<&Value> {
|
||||
self.field_values
|
||||
.iter()
|
||||
@@ -87,12 +88,12 @@ impl Document {
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Returns the first `FieldValue` associated the given field
|
||||
pub fn get_first(&self, field: Field) -> Option<&Value> {
|
||||
self.field_values
|
||||
.iter()
|
||||
.filter(|field_value| field_value.field() == field)
|
||||
.find(|field_value| field_value.field() == field)
|
||||
.map(|field_value| field_value.value())
|
||||
.next()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,7 +126,7 @@ mod tests {
|
||||
let text_field = schema_builder.add_text_field("title", TEXT);
|
||||
let mut doc = Document::default();
|
||||
doc.add_text(text_field, "My title");
|
||||
assert_eq!(doc.get_fields().len(), 1);
|
||||
assert_eq!(doc.field_values().len(), 1);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,15 +10,23 @@ use schema::Value;
|
||||
|
||||
|
||||
/// A `FieldType` describes the type (text, u32) of a field as well as
|
||||
/// how it should be handled by tantivy.
|
||||
/// how it should be handled by tantivy.
|
||||
#[derive(Clone, Debug, RustcDecodable, RustcEncodable)]
|
||||
pub enum FieldType {
|
||||
/// String field type configuration
|
||||
Str(TextOptions),
|
||||
/// U32 field type configuration
|
||||
U32(U32Options),
|
||||
}
|
||||
|
||||
impl FieldType {
|
||||
pub fn value_from_json(&self, json: &Json) -> Result<Value, ValueParsingError> {
|
||||
|
||||
/// Parses a field value from json, given the target FieldType.
|
||||
///
|
||||
/// Tantivy will not try to cast values.
|
||||
/// For instance, If the json value is the integer `3` and the
|
||||
/// target field is a `Str`, this method will return an Error.
|
||||
pub fn value_from_json(&self, json: &Json) -> Result<Value, ValueParsingError> {
|
||||
match *json {
|
||||
Json::String(ref field_text) => {
|
||||
match *self {
|
||||
@@ -66,9 +74,15 @@ pub struct FieldEntry {
|
||||
field_type: FieldType,
|
||||
}
|
||||
|
||||
|
||||
/// Possible error that may occur while parsing a field value
|
||||
/// At this point the JSON is known to be valid.
|
||||
#[derive(Debug)]
|
||||
pub enum ValueParsingError {
|
||||
/// Encounterred a numerical value that overflows or underflow its integer type.
|
||||
OverflowError(String),
|
||||
/// The json node is not of the correct type. (e.g. 3 for a `Str` type or `"abc"` for a u32 type)
|
||||
/// Tantivy will try to autocast values.
|
||||
TypeError(String),
|
||||
}
|
||||
|
||||
|
||||
@@ -9,15 +9,26 @@ use schema::Value;
|
||||
/// `FieldValue` holds together a `Field` and its `Value`.
|
||||
#[derive(Debug, Clone, Ord, PartialEq, Eq, PartialOrd, RustcEncodable, RustcDecodable)]
|
||||
pub struct FieldValue {
|
||||
pub field: Field,
|
||||
pub value: Value,
|
||||
field: Field,
|
||||
value: Value,
|
||||
}
|
||||
|
||||
impl FieldValue {
|
||||
|
||||
/// Constructor
|
||||
pub fn new(field: Field, value: Value) -> FieldValue {
|
||||
FieldValue {
|
||||
field: field,
|
||||
value: value,
|
||||
}
|
||||
}
|
||||
|
||||
/// Field accessor
|
||||
pub fn field(&self) -> Field {
|
||||
self.field
|
||||
}
|
||||
|
||||
/// Value accessor
|
||||
pub fn value(&self,) -> &Value {
|
||||
&self.value
|
||||
}
|
||||
@@ -34,10 +45,7 @@ impl BinarySerializable for FieldValue {
|
||||
fn deserialize(reader: &mut Read) -> io::Result<Self> {
|
||||
let field = try!(Field::deserialize(reader));
|
||||
let value = try!(Value::deserialize(reader));
|
||||
Ok(FieldValue {
|
||||
field: field,
|
||||
value: value,
|
||||
})
|
||||
Ok(FieldValue::new(field, value))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -133,6 +133,12 @@ pub use self::u32_options::FAST;
|
||||
|
||||
use regex::Regex;
|
||||
|
||||
|
||||
/// Validator for a potential `field_name`.
|
||||
/// Returns true iff the name can be use for a field name.
|
||||
///
|
||||
/// Anything containing more than one (alphanumeric or `_`) character
|
||||
/// is a valid field name.
|
||||
pub fn is_valid_field_name(field_name: &str) -> bool {
|
||||
lazy_static! {
|
||||
static ref FIELD_NAME_PTN: Regex = Regex::new("[_a-zA-Z0-9]+").unwrap();
|
||||
|
||||
+28
-17
@@ -79,7 +79,9 @@ impl SchemaBuilder {
|
||||
let field_entry = FieldEntry::new_text(field_name, field_options);
|
||||
self.add_field(field_entry)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Adds a field entry to the schema in build.
|
||||
fn add_field(&mut self, field_entry: FieldEntry) -> Field {
|
||||
let field = Field(self.fields.len() as u8);
|
||||
let field_name = field_entry.name().clone();
|
||||
@@ -87,7 +89,10 @@ impl SchemaBuilder {
|
||||
self.fields_map.insert(field_name, field);
|
||||
field
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Finalize the creation of a `Schema`
|
||||
/// This will consume your `SchemaBuilder`
|
||||
pub fn build(self,) -> Schema {
|
||||
Schema(Arc::new(InnerSchema {
|
||||
fields: self.fields,
|
||||
@@ -138,16 +143,19 @@ struct InnerSchema {
|
||||
pub struct Schema(Arc<InnerSchema>);
|
||||
|
||||
impl Schema {
|
||||
|
||||
|
||||
/// Return the `FieldEntry` associated to a `Field`.
|
||||
pub fn get_field_entry(&self, field: Field) -> &FieldEntry {
|
||||
&self.0.fields[field.0 as usize]
|
||||
}
|
||||
|
||||
/// Return the field name for a given `Field`.
|
||||
pub fn get_field_name(&self, field: Field) -> &String {
|
||||
self.get_field_entry(field).name()
|
||||
}
|
||||
|
||||
pub fn fields(&self,) -> &Vec<FieldEntry> {
|
||||
|
||||
/// Return the list of all the `Field`s.
|
||||
pub fn fields(&self,) -> &[FieldEntry] {
|
||||
&self.0.fields
|
||||
}
|
||||
|
||||
@@ -166,7 +174,7 @@ impl Schema {
|
||||
|
||||
pub fn to_named_doc(&self, doc: &Document) -> NamedFieldDocument {
|
||||
let mut field_map = BTreeMap::new();
|
||||
for (field, field_values) in doc.get_sorted_fields() {
|
||||
for (field, field_values) in doc.get_sorted_field_values() {
|
||||
let field_name = self.get_field_name(field);
|
||||
let values: Vec<Value> = field_values
|
||||
.into_iter()
|
||||
@@ -177,9 +185,12 @@ impl Schema {
|
||||
}
|
||||
NamedFieldDocument(field_map)
|
||||
}
|
||||
|
||||
|
||||
|
||||
/// Encode the schema in JSON.
|
||||
///
|
||||
/// Encoding a document cannot fail.
|
||||
pub fn to_json(&self, doc: &Document) -> String {
|
||||
// encoding a document cannot fail.
|
||||
json::encode(&self.to_named_doc(doc)).unwrap()
|
||||
}
|
||||
|
||||
@@ -212,10 +223,7 @@ impl Schema {
|
||||
.value_from_json(&json_item)
|
||||
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))
|
||||
);
|
||||
doc.add(FieldValue {
|
||||
field: field,
|
||||
value: value
|
||||
});
|
||||
doc.add(FieldValue::new(field, value));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
@@ -224,10 +232,7 @@ impl Schema {
|
||||
.value_from_json(&json_value)
|
||||
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))
|
||||
);
|
||||
doc.add(FieldValue {
|
||||
field: field,
|
||||
value: value
|
||||
});
|
||||
doc.add(FieldValue::new(field, value));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -285,11 +290,17 @@ impl From<SchemaBuilder> for Schema {
|
||||
|
||||
|
||||
|
||||
/// Error that may happen when deserializing
|
||||
/// a document from JSON.
|
||||
#[derive(Debug)]
|
||||
pub enum DocParsingError {
|
||||
/// The payload given is not valid JSON.
|
||||
NotJSON(json::ParserError),
|
||||
/// The payload given is not a JSON Object (`{...}`).
|
||||
NotJSONObject(String),
|
||||
/// One of the value node could not be parsed.
|
||||
ValueError(String, ValueParsingError),
|
||||
/// The json-document contains a field that is not declared in the schema.
|
||||
NoSuchFieldInSchema(String),
|
||||
}
|
||||
|
||||
@@ -379,7 +390,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
{
|
||||
let doc = schema.parse_document("{}").unwrap();
|
||||
assert!(doc.get_fields().is_empty());
|
||||
assert!(doc.field_values().is_empty());
|
||||
}
|
||||
{
|
||||
let doc = schema.parse_document(r#"{
|
||||
|
||||
+24
-5
@@ -5,31 +5,42 @@ use super::Field;
|
||||
|
||||
|
||||
|
||||
|
||||
/// Term represents the value that the token can take.
|
||||
///
|
||||
/// It actually wraps a `Vec<u8>`.
|
||||
#[derive(Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
|
||||
pub struct Term(Vec<u8>);
|
||||
|
||||
impl Term {
|
||||
|
||||
/// Pre-allocate a term buffer.
|
||||
pub fn allocate(field: Field, num_bytes: usize) -> Term {
|
||||
let mut term = Term(Vec::with_capacity(num_bytes));
|
||||
field.serialize(&mut term.0).expect("Serializing term in a Vec should never fail");
|
||||
term
|
||||
}
|
||||
|
||||
/// Set the content of the term.
|
||||
pub fn set_content(&mut self, content: &[u8]) {
|
||||
self.0.resize(content.len(), 0u8);
|
||||
(&mut self.0[..]).clone_from_slice(content);
|
||||
}
|
||||
|
||||
/// Returns the field id.
|
||||
fn field_id(&self,) -> u8 {
|
||||
self.0[0]
|
||||
}
|
||||
|
||||
pub fn get_field(&self,) -> Field {
|
||||
/// Returns the field.
|
||||
pub fn field(&self,) -> Field {
|
||||
Field(self.field_id())
|
||||
}
|
||||
|
||||
/// Builds a term given a field, and a u32-value
|
||||
///
|
||||
/// Assuming the term has a field id of 1, and a u32 value of 3234,
|
||||
/// the Term will have 5 bytes.
|
||||
/// The first byte is `1`, and the 4 following bytes are that of the u32.
|
||||
pub fn from_field_u32(field: Field, val: u32) -> Term {
|
||||
let mut buffer = Vec::with_capacity(1 + 4);
|
||||
buffer.clear();
|
||||
@@ -37,7 +48,13 @@ impl Term {
|
||||
val.serialize(&mut buffer).unwrap();
|
||||
Term(buffer)
|
||||
}
|
||||
|
||||
|
||||
/// Builds a term given a field, and a string value
|
||||
///
|
||||
/// Assuming the term has a field id of 2, and a text value of "abc",
|
||||
/// the Term will have 4 bytes.
|
||||
/// The first byte is 2, and the three following bytes are the utf-8
|
||||
/// representation of "abc".
|
||||
pub fn from_field_text(field: Field, text: &str) -> Term {
|
||||
let mut buffer = Vec::with_capacity(1 + text.len());
|
||||
buffer.clear();
|
||||
@@ -46,11 +63,13 @@ impl Term {
|
||||
Term(buffer)
|
||||
}
|
||||
|
||||
/// Set the texts only, keeping the field untouched.
|
||||
pub fn set_text(&mut self, text: &str) {
|
||||
self.0.resize(1, 0u8);
|
||||
self.0.extend(text.as_bytes());
|
||||
}
|
||||
|
||||
/// Returns the underlying `&[u8]`
|
||||
pub fn as_slice(&self,)->&[u8] {
|
||||
&self.0
|
||||
}
|
||||
@@ -89,13 +108,13 @@ mod tests {
|
||||
let count_field = schema_builder.add_text_field("count", STRING);
|
||||
{
|
||||
let term = Term::from_field_text(title_field, "test");
|
||||
assert_eq!(term.get_field(), title_field);
|
||||
assert_eq!(term.field(), title_field);
|
||||
assert_eq!(term.as_slice()[0], 1u8);
|
||||
assert_eq!(&term.as_slice()[1..], "test".as_bytes());
|
||||
}
|
||||
{
|
||||
let term = Term::from_field_u32(count_field, 983u32);
|
||||
assert_eq!(term.get_field(), count_field);
|
||||
assert_eq!(term.field(), count_field);
|
||||
assert_eq!(term.as_slice()[0], 2u8);
|
||||
assert_eq!(term.as_slice().len(), 5);
|
||||
assert_eq!(term.as_slice()[1], (983u32 % 256u32) as u8);
|
||||
|
||||
@@ -5,7 +5,7 @@ use rustc_serialize::Encodable;
|
||||
use rustc_serialize::Encoder;
|
||||
|
||||
|
||||
/// Text options
|
||||
/// Define how a text field should be handled by tantivy.
|
||||
#[derive(Clone,Debug,PartialEq,Eq, RustcDecodable, RustcEncodable)]
|
||||
pub struct TextOptions {
|
||||
indexing: TextIndexingOptions,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/// Define how a U32 field should be handled by tantivy.
|
||||
#[derive(Clone,Debug,PartialEq,Eq, RustcDecodable, RustcEncodable)]
|
||||
pub struct U32Options {
|
||||
indexed: bool,
|
||||
|
||||
@@ -4,13 +4,21 @@ use std::io;
|
||||
use std::io::Write;
|
||||
use std::io::Read;
|
||||
|
||||
/// Value represents the value of a any field.
|
||||
/// It is an enum over all over all of the possible field type.
|
||||
#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, RustcEncodable, RustcDecodable)]
|
||||
pub enum Value {
|
||||
/// The str type is used for any text information.
|
||||
Str(String),
|
||||
/// Unsigned 32-bits Integer `u32`
|
||||
U32(u32),
|
||||
}
|
||||
|
||||
impl Value {
|
||||
/// Returns the text value, provided the value is of the `Str` type.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the value is not of type `Str`
|
||||
pub fn text(&self) -> &str {
|
||||
match *self {
|
||||
Value::Str(ref text) => {
|
||||
@@ -22,6 +30,10 @@ impl Value {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the u32-value, provided the value is of the `U32` type.
|
||||
///
|
||||
/// # Panics
|
||||
/// If the value is not of type `U32`
|
||||
pub fn u32_value(&self) -> u32 {
|
||||
match *self {
|
||||
Value::U32(ref value) => {
|
||||
|
||||
+2
-8
@@ -30,18 +30,12 @@ mod tests {
|
||||
for i in 0..1000 {
|
||||
let mut fields: Vec<FieldValue> = Vec::new();
|
||||
{
|
||||
let field_value = FieldValue {
|
||||
field: field_body,
|
||||
value: From::from(lorem.clone())
|
||||
};
|
||||
let field_value = FieldValue::new(field_body, From::from(lorem.clone()));
|
||||
fields.push(field_value);
|
||||
}
|
||||
{
|
||||
let title_text = format!("Doc {}", i);
|
||||
let field_value = FieldValue {
|
||||
field: field_title,
|
||||
value: From::from(title_text)
|
||||
};
|
||||
let field_value = FieldValue::new(field_title, From::from(title_text));
|
||||
fields.push(field_value);
|
||||
}
|
||||
let fields_refs: Vec<&FieldValue> = fields.iter().collect();
|
||||
|
||||
Reference in New Issue
Block a user