mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-06 17:22:54 +00:00
Compare commits
2 Commits
incrementa
...
crate-divi
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
72fc1c10a6 | ||
|
|
b28654c3fb |
19
Cargo.toml
19
Cargo.toml
@@ -16,22 +16,22 @@ edition = "2018"
|
|||||||
base64 = "0.10.0"
|
base64 = "0.10.0"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
regex ={version = "1.3.0", default-features = false, features = ["std"]}
|
regex = "1.0"
|
||||||
tantivy-fst = "0.1"
|
tantivy-fst = "0.1"
|
||||||
memmap = {version = "0.7", optional=true}
|
memmap = {version = "0.7", optional=true}
|
||||||
lz4 = {version="1.20", optional=true}
|
lz4 = {version="1.20", optional=true}
|
||||||
snap = {version="0.2"}
|
snap = {version="0.2"}
|
||||||
derive_builder = "0.7"
|
|
||||||
atomicwrites = {version="0.2.2", optional=true}
|
atomicwrites = {version="0.2.2", optional=true}
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
|
combine = ">=3.6.0,<4.0.0"
|
||||||
serde = "1.0"
|
serde = "1.0"
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
num_cpus = "1.2"
|
num_cpus = "1.2"
|
||||||
fs2={version="0.4", optional=true}
|
fs2={version="0.4", optional=true}
|
||||||
itertools = "0.8"
|
itertools = "0.8"
|
||||||
levenshtein_automata = "0.1"
|
levenshtein_automata = {version="0.1", features=["fst_automaton"]}
|
||||||
notify = {version="4", optional=true}
|
notify = {version="4", optional=true}
|
||||||
bit-set = "0.5"
|
bit-set = "0.5"
|
||||||
uuid = { version = "0.7.2", features = ["v4", "serde"] }
|
uuid = { version = "0.7.2", features = ["v4", "serde"] }
|
||||||
@@ -42,7 +42,6 @@ owning_ref = "0.4"
|
|||||||
stable_deref_trait = "1.0.0"
|
stable_deref_trait = "1.0.0"
|
||||||
rust-stemmers = "1.1"
|
rust-stemmers = "1.1"
|
||||||
downcast-rs = { version="1.0" }
|
downcast-rs = { version="1.0" }
|
||||||
tantivy-query-grammar = { path="./query-grammar" }
|
|
||||||
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
||||||
census = "0.2"
|
census = "0.2"
|
||||||
fnv = "1.0.6"
|
fnv = "1.0.6"
|
||||||
@@ -55,6 +54,10 @@ murmurhash32 = "0.2"
|
|||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
smallvec = "0.6"
|
smallvec = "0.6"
|
||||||
|
|
||||||
|
tantivy-schema = {path= "./tantivy-schema"}
|
||||||
|
tantivy-tokenizer = {path= "./tantivy-tokenizer"}
|
||||||
|
tantivy-common = {path="./tantivy-common"}
|
||||||
|
|
||||||
[target.'cfg(windows)'.dependencies]
|
[target.'cfg(windows)'.dependencies]
|
||||||
winapi = "0.3"
|
winapi = "0.3"
|
||||||
|
|
||||||
@@ -81,14 +84,10 @@ failpoints = ["fail/failpoints"]
|
|||||||
unstable = [] # useful for benches.
|
unstable = [] # useful for benches.
|
||||||
wasm-bindgen = ["uuid/wasm-bindgen"]
|
wasm-bindgen = ["uuid/wasm-bindgen"]
|
||||||
|
|
||||||
[workspace]
|
|
||||||
members = ["query-grammar", "incremental-search"]
|
|
||||||
|
|
||||||
[badges]
|
[badges]
|
||||||
travis-ci = { repository = "tantivy-search/tantivy" }
|
travis-ci = { repository = "tantivy-search/tantivy" }
|
||||||
|
|
||||||
[dev-dependencies.fail]
|
[dev-dependencies.fail]
|
||||||
version = "0.3"
|
|
||||||
features = ["failpoints"]
|
features = ["failpoints"]
|
||||||
|
|
||||||
# Following the "fail" crate best practises, we isolate
|
# Following the "fail" crate best practises, we isolate
|
||||||
@@ -102,3 +101,7 @@ features = ["failpoints"]
|
|||||||
name = "failpoints"
|
name = "failpoints"
|
||||||
path = "tests/failpoints/mod.rs"
|
path = "tests/failpoints/mod.rs"
|
||||||
required-features = ["fail/failpoints"]
|
required-features = ["fail/failpoints"]
|
||||||
|
|
||||||
|
|
||||||
|
[workspace]
|
||||||
|
members = ["tantivy-schema", "tantivy-common", "tantivy-tokenizer"]
|
||||||
|
|||||||
3
Makefile
3
Makefile
@@ -1,3 +0,0 @@
|
|||||||
test:
|
|
||||||
echo "Run test only... No examples."
|
|
||||||
cargo test --all --tests --lib
|
|
||||||
@@ -7,7 +7,7 @@ set -ex
|
|||||||
main() {
|
main() {
|
||||||
if [ ! -z $CODECOV ]; then
|
if [ ! -z $CODECOV ]; then
|
||||||
echo "Codecov"
|
echo "Codecov"
|
||||||
cargo build --verbose && cargo coverage --verbose --all && bash <(curl -s https://codecov.io/bash) -s target/kcov
|
cargo build --verbose && cargo coverage --verbose && bash <(curl -s https://codecov.io/bash) -s target/kcov
|
||||||
else
|
else
|
||||||
echo "Build"
|
echo "Build"
|
||||||
cross build --target $TARGET
|
cross build --target $TARGET
|
||||||
@@ -15,8 +15,7 @@ main() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo "Test"
|
echo "Test"
|
||||||
cross test --target $TARGET --no-default-features --features mmap
|
cross test --target $TARGET --no-default-features --features mmap -- --test-threads 1
|
||||||
cross test --target $TARGET --no-default-features --features mmap query-grammar
|
|
||||||
fi
|
fi
|
||||||
for example in $(ls examples/*.rs)
|
for example in $(ls examples/*.rs)
|
||||||
do
|
do
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ fn main() -> tantivy::Result<()> {
|
|||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
// we index 100 times the document... for the sake of the example.
|
// we index 100 times the document... for the sake of the example.
|
||||||
for i in 0..100 {
|
for i in 0..100 {
|
||||||
let opstamp = index_writer_clone_1
|
let opstamp = {
|
||||||
.read().unwrap() //< A read lock is sufficient here.
|
// A read lock is sufficient here.
|
||||||
.add_document(
|
let index_writer_rlock = index_writer_clone_1.read().unwrap();
|
||||||
|
index_writer_rlock.add_document(
|
||||||
doc!(
|
doc!(
|
||||||
title => "Of Mice and Men",
|
title => "Of Mice and Men",
|
||||||
body => "A few miles south of Soledad, the Salinas River drops in close to the hillside \
|
body => "A few miles south of Soledad, the Salinas River drops in close to the hillside \
|
||||||
@@ -59,7 +60,8 @@ fn main() -> tantivy::Result<()> {
|
|||||||
fresh and green with every spring, carrying in their lower leaf junctures the \
|
fresh and green with every spring, carrying in their lower leaf junctures the \
|
||||||
debris of the winter’s flooding; and sycamores with mottled, white, recumbent \
|
debris of the winter’s flooding; and sycamores with mottled, white, recumbent \
|
||||||
limbs and branches that arch over the pool"
|
limbs and branches that arch over the pool"
|
||||||
));
|
))
|
||||||
|
};
|
||||||
println!("add doc {} from thread 1 - opstamp {}", i, opstamp);
|
println!("add doc {} from thread 1 - opstamp {}", i, opstamp);
|
||||||
thread::sleep(Duration::from_millis(20));
|
thread::sleep(Duration::from_millis(20));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,395 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::u64;
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
|
||||||
pub(crate) struct TinySet(u64);
|
|
||||||
|
|
||||||
impl fmt::Debug for TinySet {
|
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
|
||||||
self.into_iter().collect::<Vec<u32>>().fmt(f)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct TinySetIterator(TinySet);
|
|
||||||
impl Iterator for TinySetIterator {
|
|
||||||
type Item = u32;
|
|
||||||
|
|
||||||
fn next(&mut self) -> Option<Self::Item> {
|
|
||||||
self.0.pop_lowest()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IntoIterator for TinySet {
|
|
||||||
type Item = u32;
|
|
||||||
type IntoIter = TinySetIterator;
|
|
||||||
fn into_iter(self) -> Self::IntoIter {
|
|
||||||
TinySetIterator(self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl TinySet {
|
|
||||||
/// Returns an empty `TinySet`.
|
|
||||||
pub fn empty() -> TinySet {
|
|
||||||
TinySet(0u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the complement of the set in `[0, 64[`.
|
|
||||||
fn complement(self) -> TinySet {
|
|
||||||
TinySet(!self.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true iff the `TinySet` contains the element `el`.
|
|
||||||
pub fn contains(self, el: u32) -> bool {
|
|
||||||
!self.intersect(TinySet::singleton(el)).is_empty()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the intersection of `self` and `other`
|
|
||||||
pub fn intersect(self, other: TinySet) -> TinySet {
|
|
||||||
TinySet(self.0 & other.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new `TinySet` containing only one element
|
|
||||||
/// within `[0; 64[`
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn singleton(el: u32) -> TinySet {
|
|
||||||
TinySet(1u64 << u64::from(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a new element within [0..64[
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn insert(self, el: u32) -> TinySet {
|
|
||||||
self.union(TinySet::singleton(el))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Insert a new element within [0..64[
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn insert_mut(&mut self, el: u32) -> bool {
|
|
||||||
let old = *self;
|
|
||||||
*self = old.insert(el);
|
|
||||||
old != *self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the union of two tinysets
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn union(self, other: TinySet) -> TinySet {
|
|
||||||
TinySet(self.0 | other.0)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true iff the `TinySet` is empty.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn is_empty(self) -> bool {
|
|
||||||
self.0 == 0u64
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the lowest element in the `TinySet`
|
|
||||||
/// and removes it.
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn pop_lowest(&mut self) -> Option<u32> {
|
|
||||||
if self.is_empty() {
|
|
||||||
None
|
|
||||||
} else {
|
|
||||||
let lowest = self.0.trailing_zeros() as u32;
|
|
||||||
self.0 ^= TinySet::singleton(lowest).0;
|
|
||||||
Some(lowest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a `TinySet` than contains all values up
|
|
||||||
/// to limit excluded.
|
|
||||||
///
|
|
||||||
/// The limit is assumed to be strictly lower than 64.
|
|
||||||
pub fn range_lower(upper_bound: u32) -> TinySet {
|
|
||||||
TinySet((1u64 << u64::from(upper_bound % 64u32)) - 1u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns a `TinySet` that contains all values greater
|
|
||||||
/// or equal to the given limit, included. (and up to 63)
|
|
||||||
///
|
|
||||||
/// The limit is assumed to be strictly lower than 64.
|
|
||||||
pub fn range_greater_or_equal(from_included: u32) -> TinySet {
|
|
||||||
TinySet::range_lower(from_included).complement()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
self.0 = 0u64;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn len(self) -> u32 {
|
|
||||||
self.0.count_ones()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct BitSet {
|
|
||||||
tinysets: Box<[TinySet]>,
|
|
||||||
len: usize, //< Technically it should be u32, but we
|
|
||||||
// count multiple inserts.
|
|
||||||
// `usize` guards us from overflow.
|
|
||||||
max_value: u32,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn num_buckets(max_val: u32) -> u32 {
|
|
||||||
(max_val + 63u32) / 64u32
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BitSet {
|
|
||||||
/// Create a new `BitSet` that may contain elements
|
|
||||||
/// within `[0, max_val[`.
|
|
||||||
pub fn with_max_value(max_value: u32) -> BitSet {
|
|
||||||
let num_buckets = num_buckets(max_value);
|
|
||||||
let tinybisets = vec![TinySet::empty(); num_buckets as usize].into_boxed_slice();
|
|
||||||
BitSet {
|
|
||||||
tinysets: tinybisets,
|
|
||||||
len: 0,
|
|
||||||
max_value,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Removes all elements from the `BitSet`.
|
|
||||||
pub fn clear(&mut self) {
|
|
||||||
for tinyset in self.tinysets.iter_mut() {
|
|
||||||
*tinyset = TinySet::empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the number of elements in the `BitSet`.
|
|
||||||
pub fn len(&self) -> usize {
|
|
||||||
self.len
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Inserts an element in the `BitSet`
|
|
||||||
pub fn insert(&mut self, el: u32) {
|
|
||||||
// we do not check saturated els.
|
|
||||||
let higher = el / 64u32;
|
|
||||||
let lower = el % 64u32;
|
|
||||||
self.len += if self.tinysets[higher as usize].insert_mut(lower) {
|
|
||||||
1
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true iff the elements is in the `BitSet`.
|
|
||||||
pub fn contains(&self, el: u32) -> bool {
|
|
||||||
self.tinyset(el / 64u32).contains(el % 64)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the first non-empty `TinySet` associated to a bucket lower
|
|
||||||
/// or greater than bucket.
|
|
||||||
///
|
|
||||||
/// Reminder: the tiny set with the bucket `bucket`, represents the
|
|
||||||
/// elements from `bucket * 64` to `(bucket+1) * 64`.
|
|
||||||
pub(crate) fn first_non_empty_bucket(&self, bucket: u32) -> Option<u32> {
|
|
||||||
self.tinysets[bucket as usize..]
|
|
||||||
.iter()
|
|
||||||
.cloned()
|
|
||||||
.position(|tinyset| !tinyset.is_empty())
|
|
||||||
.map(|delta_bucket| bucket + delta_bucket as u32)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn max_value(&self) -> u32 {
|
|
||||||
self.max_value
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the tiny bitset representing the
|
|
||||||
/// the set restricted to the number range from
|
|
||||||
/// `bucket * 64` to `(bucket + 1) * 64`.
|
|
||||||
pub(crate) fn tinyset(&self, bucket: u32) -> TinySet {
|
|
||||||
self.tinysets[bucket as usize]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
|
|
||||||
use super::BitSet;
|
|
||||||
use super::TinySet;
|
|
||||||
use crate::docset::DocSet;
|
|
||||||
use crate::query::BitSetDocSet;
|
|
||||||
use crate::tests;
|
|
||||||
use crate::tests::generate_nonunique_unsorted;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tiny_set() {
|
|
||||||
assert!(TinySet::empty().is_empty());
|
|
||||||
{
|
|
||||||
let mut u = TinySet::empty().insert(1u32);
|
|
||||||
assert_eq!(u.pop_lowest(), Some(1u32));
|
|
||||||
assert!(u.pop_lowest().is_none())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut u = TinySet::empty().insert(1u32).insert(1u32);
|
|
||||||
assert_eq!(u.pop_lowest(), Some(1u32));
|
|
||||||
assert!(u.pop_lowest().is_none())
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut u = TinySet::empty().insert(2u32);
|
|
||||||
assert_eq!(u.pop_lowest(), Some(2u32));
|
|
||||||
u.insert_mut(1u32);
|
|
||||||
assert_eq!(u.pop_lowest(), Some(1u32));
|
|
||||||
assert!(u.pop_lowest().is_none());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let mut u = TinySet::empty().insert(63u32);
|
|
||||||
assert_eq!(u.pop_lowest(), Some(63u32));
|
|
||||||
assert!(u.pop_lowest().is_none());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset() {
|
|
||||||
let test_against_hashset = |els: &[u32], max_value: u32| {
|
|
||||||
let mut hashset: HashSet<u32> = HashSet::new();
|
|
||||||
let mut bitset = BitSet::with_max_value(max_value);
|
|
||||||
for &el in els {
|
|
||||||
assert!(el < max_value);
|
|
||||||
hashset.insert(el);
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
for el in 0..max_value {
|
|
||||||
assert_eq!(hashset.contains(&el), bitset.contains(el));
|
|
||||||
}
|
|
||||||
assert_eq!(bitset.max_value(), max_value);
|
|
||||||
};
|
|
||||||
|
|
||||||
test_against_hashset(&[], 0);
|
|
||||||
test_against_hashset(&[], 1);
|
|
||||||
test_against_hashset(&[0u32], 1);
|
|
||||||
test_against_hashset(&[0u32], 100);
|
|
||||||
test_against_hashset(&[1u32, 2u32], 4);
|
|
||||||
test_against_hashset(&[99u32], 100);
|
|
||||||
test_against_hashset(&[63u32], 64);
|
|
||||||
test_against_hashset(&[62u32, 63u32], 64);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset_large() {
|
|
||||||
let arr = generate_nonunique_unsorted(100_000, 5_000);
|
|
||||||
let mut btreeset: BTreeSet<u32> = BTreeSet::new();
|
|
||||||
let mut bitset = BitSet::with_max_value(100_000);
|
|
||||||
for el in arr {
|
|
||||||
btreeset.insert(el);
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
for i in 0..100_000 {
|
|
||||||
assert_eq!(btreeset.contains(&i), bitset.contains(i));
|
|
||||||
}
|
|
||||||
assert_eq!(btreeset.len(), bitset.len());
|
|
||||||
let mut bitset_docset = BitSetDocSet::from(bitset);
|
|
||||||
for el in btreeset.into_iter() {
|
|
||||||
bitset_docset.advance();
|
|
||||||
assert_eq!(bitset_docset.doc(), el);
|
|
||||||
}
|
|
||||||
assert!(!bitset_docset.advance());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset_num_buckets() {
|
|
||||||
use super::num_buckets;
|
|
||||||
assert_eq!(num_buckets(0u32), 0);
|
|
||||||
assert_eq!(num_buckets(1u32), 1);
|
|
||||||
assert_eq!(num_buckets(64u32), 1);
|
|
||||||
assert_eq!(num_buckets(65u32), 2);
|
|
||||||
assert_eq!(num_buckets(128u32), 2);
|
|
||||||
assert_eq!(num_buckets(129u32), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_tinyset_range() {
|
|
||||||
assert_eq!(
|
|
||||||
TinySet::range_lower(3).into_iter().collect::<Vec<u32>>(),
|
|
||||||
[0, 1, 2]
|
|
||||||
);
|
|
||||||
assert!(TinySet::range_lower(0).is_empty());
|
|
||||||
assert_eq!(
|
|
||||||
TinySet::range_lower(63).into_iter().collect::<Vec<u32>>(),
|
|
||||||
(0u32..63u32).collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
TinySet::range_lower(1).into_iter().collect::<Vec<u32>>(),
|
|
||||||
[0]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
TinySet::range_lower(2).into_iter().collect::<Vec<u32>>(),
|
|
||||||
[0, 1]
|
|
||||||
);
|
|
||||||
assert_eq!(
|
|
||||||
TinySet::range_greater_or_equal(3)
|
|
||||||
.into_iter()
|
|
||||||
.collect::<Vec<u32>>(),
|
|
||||||
(3u32..64u32).collect::<Vec<_>>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset_len() {
|
|
||||||
let mut bitset = BitSet::with_max_value(1_000);
|
|
||||||
assert_eq!(bitset.len(), 0);
|
|
||||||
bitset.insert(3u32);
|
|
||||||
assert_eq!(bitset.len(), 1);
|
|
||||||
bitset.insert(103u32);
|
|
||||||
assert_eq!(bitset.len(), 2);
|
|
||||||
bitset.insert(3u32);
|
|
||||||
assert_eq!(bitset.len(), 2);
|
|
||||||
bitset.insert(103u32);
|
|
||||||
assert_eq!(bitset.len(), 2);
|
|
||||||
bitset.insert(104u32);
|
|
||||||
assert_eq!(bitset.len(), 3);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset_clear() {
|
|
||||||
let mut bitset = BitSet::with_max_value(1_000);
|
|
||||||
let els = tests::sample(1_000, 0.01f64);
|
|
||||||
for &el in &els {
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
assert!(els.iter().all(|el| bitset.contains(*el)));
|
|
||||||
bitset.clear();
|
|
||||||
for el in 0u32..1000u32 {
|
|
||||||
assert!(!bitset.contains(el));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
|
||||||
mod bench {
|
|
||||||
|
|
||||||
use super::BitSet;
|
|
||||||
use super::TinySet;
|
|
||||||
use test;
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_tinyset_pop(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut tinyset = TinySet::singleton(test::black_box(31u32));
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_tinyset_sum(b: &mut test::Bencher) {
|
|
||||||
let tiny_set = TinySet::empty().insert(10u32).insert(14u32).insert(21u32);
|
|
||||||
b.iter(|| {
|
|
||||||
assert_eq!(test::black_box(tiny_set).into_iter().sum::<u32>(), 45u32);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_tinyarr_sum(b: &mut test::Bencher) {
|
|
||||||
let v = [10u32, 14u32, 21u32];
|
|
||||||
b.iter(|| test::black_box(v).iter().cloned().sum::<u32>());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
|
||||||
fn bench_bitset_initialize(b: &mut test::Bencher) {
|
|
||||||
b.iter(|| BitSet::with_max_value(1_000_000));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,266 +0,0 @@
|
|||||||
use tantivy::query::{BooleanQuery, FuzzyTermQuery, EmptyQuery};
|
|
||||||
use derive_builder::Builder;
|
|
||||||
use std::str::FromStr;
|
|
||||||
use tantivy::query::{FuzzyConfiguration, FuzzyConfigurationBuilder, Query, Occur};
|
|
||||||
use tantivy::schema::Field;
|
|
||||||
use tantivy::{Searcher, TantivyError, DocAddress, Term, Document};
|
|
||||||
use tantivy::collector::TopDocs;
|
|
||||||
use std::ops::Deref;
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct IncrementalSearchQuery {
|
|
||||||
pub terms: Vec<String>,
|
|
||||||
pub last_is_prefix: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IncrementalSearchQuery {
|
|
||||||
pub fn fuzzy_configurations(&self) -> Vec<FuzzyConfigurations> {
|
|
||||||
if self.terms.is_empty() {
|
|
||||||
return Vec::default();
|
|
||||||
}
|
|
||||||
let single_term_confs: Vec<FuzzyConfigurationBuilder> = (0u8..3u8)
|
|
||||||
.map(|d: u8| {
|
|
||||||
let mut builder = FuzzyConfigurationBuilder::default();
|
|
||||||
builder.distance(d).transposition_cost_one(true);
|
|
||||||
builder
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
let mut configurations: Vec<Vec<FuzzyConfigurationBuilder>> = single_term_confs
|
|
||||||
.iter()
|
|
||||||
.map(|conf| vec![conf.clone()])
|
|
||||||
.collect();
|
|
||||||
let mut new_configurations = Vec::new();
|
|
||||||
for _ in 1..self.terms.len() {
|
|
||||||
new_configurations.clear();
|
|
||||||
for single_term_conf in &single_term_confs {
|
|
||||||
for configuration in &configurations {
|
|
||||||
let mut new_configuration: Vec<FuzzyConfigurationBuilder> = configuration.clone();
|
|
||||||
new_configuration.push(single_term_conf.clone());
|
|
||||||
new_configurations.push(new_configuration);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
std::mem::swap(&mut configurations, &mut new_configurations);
|
|
||||||
}
|
|
||||||
if self.last_is_prefix {
|
|
||||||
for configuration in &mut configurations {
|
|
||||||
if let Some(last_conf) = configuration.last_mut() {
|
|
||||||
last_conf.prefix(true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let mut fuzzy_configurations: Vec<FuzzyConfigurations> = configurations
|
|
||||||
.into_iter()
|
|
||||||
.map(FuzzyConfigurations::from)
|
|
||||||
.collect();
|
|
||||||
fuzzy_configurations.sort_by(|left, right| left.cost.partial_cmp(&right.cost).unwrap());
|
|
||||||
fuzzy_configurations
|
|
||||||
}
|
|
||||||
|
|
||||||
fn search_query(&self, fields: &[Field], configurations: FuzzyConfigurations) -> Box<dyn Query> {
|
|
||||||
if self.terms.is_empty() {
|
|
||||||
Box::new(EmptyQuery)
|
|
||||||
} else if self.terms.len() == 1 {
|
|
||||||
build_query_for_fields(fields, &self.terms[0], &configurations.configurations[0])
|
|
||||||
} else {
|
|
||||||
Box::new(BooleanQuery::from(self.terms.iter()
|
|
||||||
.zip(configurations.configurations.iter())
|
|
||||||
.map(|(term, configuration)|
|
|
||||||
(Occur::Must, build_query_for_fields(fields, &term, &configuration))
|
|
||||||
)
|
|
||||||
.collect::<Vec<_>>()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct FuzzyConfigurations {
|
|
||||||
configurations: Vec<FuzzyConfiguration>,
|
|
||||||
cost: f64,
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
fn compute_cost(fuzzy_confs: &[FuzzyConfiguration]) -> f64 {
|
|
||||||
fuzzy_confs
|
|
||||||
.iter()
|
|
||||||
.map(|fuzzy_conf| {
|
|
||||||
let weight = if fuzzy_conf.prefix { 30f64 } else { 5f64 };
|
|
||||||
weight * f64::from(fuzzy_conf.distance)
|
|
||||||
})
|
|
||||||
.sum()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Vec<FuzzyConfigurationBuilder>> for FuzzyConfigurations {
|
|
||||||
fn from(fuzzy_conf_builder: Vec<FuzzyConfigurationBuilder>) -> FuzzyConfigurations {
|
|
||||||
let configurations = fuzzy_conf_builder
|
|
||||||
.into_iter()
|
|
||||||
.map(|conf| conf.build().unwrap())
|
|
||||||
.collect::<Vec<FuzzyConfiguration>>();
|
|
||||||
let cost = compute_cost(&configurations);
|
|
||||||
FuzzyConfigurations {
|
|
||||||
configurations,
|
|
||||||
cost,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct ParseIncrementalQueryError;
|
|
||||||
|
|
||||||
impl Into<TantivyError> for ParseIncrementalQueryError {
|
|
||||||
fn into(self) -> TantivyError {
|
|
||||||
TantivyError::InvalidArgument(format!("Invalid query: {:?}", self))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl FromStr for IncrementalSearchQuery {
|
|
||||||
type Err = ParseIncrementalQueryError;
|
|
||||||
|
|
||||||
fn from_str(query_str: &str) -> Result<Self, Self::Err> {
|
|
||||||
let terms: Vec<String> = query_str
|
|
||||||
.split_whitespace()
|
|
||||||
.map(ToString::to_string)
|
|
||||||
.collect();
|
|
||||||
Ok(IncrementalSearchQuery {
|
|
||||||
terms,
|
|
||||||
last_is_prefix: query_str
|
|
||||||
.chars()
|
|
||||||
.last()
|
|
||||||
.map(|c| !c.is_whitespace())
|
|
||||||
.unwrap_or(false),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_query_for_fields(fields: &[Field], term_text: &str, conf: &FuzzyConfiguration) -> Box<dyn Query> {
|
|
||||||
assert!(fields.len() > 0);
|
|
||||||
if fields.len() > 1 {
|
|
||||||
let term_queries: Vec<(Occur, Box<dyn Query>)> = fields
|
|
||||||
.iter()
|
|
||||||
.map(|&field| {
|
|
||||||
let term = Term::from_field_text(field, term_text);
|
|
||||||
let query = FuzzyTermQuery::new_from_configuration(term, conf.clone());
|
|
||||||
let boxed_query: Box<dyn Query> = Box::new(query);
|
|
||||||
(Occur::Must, boxed_query)
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
Box::new(BooleanQuery::from(term_queries))
|
|
||||||
} else {
|
|
||||||
let term = Term::from_field_text(fields[0], term_text);
|
|
||||||
Box::new( FuzzyTermQuery::new_from_configuration(term, conf.clone()))
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct IncrementalSearchResult {
|
|
||||||
pub docs: Vec<Document>
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Builder, Default)]
|
|
||||||
pub struct IncrementalSearch {
|
|
||||||
nhits: usize,
|
|
||||||
#[builder(default)]
|
|
||||||
search_fields: Vec<Field>,
|
|
||||||
#[builder(default)]
|
|
||||||
return_fields: Vec<Field>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IncrementalSearch {
|
|
||||||
|
|
||||||
pub fn search<S: Deref<Target=Searcher>>(
|
|
||||||
&self,
|
|
||||||
query: &str,
|
|
||||||
searcher: &S,
|
|
||||||
) -> tantivy::Result<IncrementalSearchResult> {
|
|
||||||
let searcher = searcher.deref();
|
|
||||||
let inc_search_query: IncrementalSearchQuery =
|
|
||||||
FromStr::from_str(query).map_err(Into::<TantivyError>::into)?;
|
|
||||||
|
|
||||||
let mut results: Vec<DocAddress> = Vec::default();
|
|
||||||
let mut remaining = self.nhits;
|
|
||||||
for fuzzy_conf in inc_search_query.fuzzy_configurations() {
|
|
||||||
if remaining == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let query = inc_search_query.search_query(&self.search_fields[..], fuzzy_conf);
|
|
||||||
let new_docs = searcher.search(query.as_ref(), &TopDocs::with_limit(remaining))?;
|
|
||||||
// TODO(pmasurel) remove already added docs.
|
|
||||||
results.extend(new_docs.into_iter()
|
|
||||||
.map(|(_, doc_address)| doc_address));
|
|
||||||
remaining = self.nhits - results.len();
|
|
||||||
if remaining == 0 {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let docs: Vec<Document> = results.into_iter()
|
|
||||||
.map(|doc_address: DocAddress| searcher.doc(doc_address))
|
|
||||||
.collect::<tantivy::Result<_>>()?;
|
|
||||||
Ok(IncrementalSearchResult {
|
|
||||||
docs
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use tantivy::doc;
|
|
||||||
use crate::{IncrementalSearch, IncrementalSearchBuilder, IncrementalSearchQuery};
|
|
||||||
use std::str::FromStr;
|
|
||||||
use tantivy::schema::{SchemaBuilder, TEXT, STORED};
|
|
||||||
use tantivy::Index;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_incremental_search() {
|
|
||||||
let incremental_search = IncrementalSearchBuilder::default()
|
|
||||||
.nhits(10)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_incremental_search_query_parse_empty() {
|
|
||||||
let query = IncrementalSearchQuery::from_str("").unwrap();
|
|
||||||
assert_eq!(query.terms, Vec::<String>::new());
|
|
||||||
assert!(!query.last_is_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_incremental_search_query_parse_trailing_whitespace() {
|
|
||||||
let query = IncrementalSearchQuery::from_str("hello happy tax pa ").unwrap();
|
|
||||||
assert_eq!(query.terms, vec!["hello", "happy", "tax", "pa"]);
|
|
||||||
assert!(!query.last_is_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_incremental_search_query_parse_unicode_whitespace() {
|
|
||||||
let query = IncrementalSearchQuery::from_str("hello happy tax pa ").unwrap();
|
|
||||||
assert_eq!(query.terms, vec!["hello", "happy", "tax", "pa"]);
|
|
||||||
assert!(!query.last_is_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_incremental_search_query_parse() {
|
|
||||||
let query = IncrementalSearchQuery::from_str("hello happy tax pa").unwrap();
|
|
||||||
assert_eq!(query.terms, vec!["hello", "happy", "tax", "pa"]);
|
|
||||||
assert!(query.last_is_prefix);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_blop() {
|
|
||||||
let mut schema_builder = SchemaBuilder::new();
|
|
||||||
let body = schema_builder.add_text_field("body", TEXT | STORED);
|
|
||||||
let schema = schema_builder.build();
|
|
||||||
let index = Index::create_in_ram(schema);
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 30_000_000).unwrap();
|
|
||||||
index_writer.add_document(doc!(body=> "hello happy tax payer"));
|
|
||||||
index_writer.commit().unwrap();
|
|
||||||
let reader = index.reader().unwrap();
|
|
||||||
let searcher = reader.searcher();
|
|
||||||
let incremental_search: IncrementalSearch = IncrementalSearchBuilder::default()
|
|
||||||
.nhits(1)
|
|
||||||
.search_fields(vec![body])
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let top_docs = incremental_search.search("hello hapy t", &searcher).unwrap();
|
|
||||||
assert_eq!(top_docs.docs.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "tantivy-query-grammar"
|
|
||||||
version = "0.11.0"
|
|
||||||
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
|
||||||
license = "MIT"
|
|
||||||
categories = ["database-implementations", "data-structures"]
|
|
||||||
description = """Search engine library"""
|
|
||||||
documentation = "https://tantivy-search.github.io/tantivy/tantivy/index.html"
|
|
||||||
homepage = "https://github.com/tantivy-search/tantivy"
|
|
||||||
repository = "https://github.com/tantivy-search/tantivy"
|
|
||||||
readme = "README.md"
|
|
||||||
keywords = ["search", "information", "retrieval"]
|
|
||||||
edition = "2018"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
combine = ">=3.6.0,<4.0.0"
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
#![recursion_limit = "100"]
|
|
||||||
|
|
||||||
mod occur;
|
|
||||||
mod query_grammar;
|
|
||||||
mod user_input_ast;
|
|
||||||
use combine::parser::Parser;
|
|
||||||
|
|
||||||
pub use crate::occur::Occur;
|
|
||||||
use crate::query_grammar::parse_to_ast;
|
|
||||||
pub use crate::user_input_ast::{UserInputAST, UserInputBound, UserInputLeaf, UserInputLiteral};
|
|
||||||
|
|
||||||
pub struct Error;
|
|
||||||
|
|
||||||
pub fn parse_query(query: &str) -> Result<UserInputAST, Error> {
|
|
||||||
let (user_input_ast, _remaining) = parse_to_ast().parse(query).map_err(|_| Error)?;
|
|
||||||
Ok(user_input_ast)
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
use crate::common::CompositeFile;
|
|
||||||
use crate::common::HasLen;
|
use crate::common::HasLen;
|
||||||
use crate::core::InvertedIndexReader;
|
use crate::core::InvertedIndexReader;
|
||||||
use crate::core::Segment;
|
use crate::core::Segment;
|
||||||
@@ -15,6 +14,7 @@ use crate::schema::Schema;
|
|||||||
use crate::space_usage::SegmentSpaceUsage;
|
use crate::space_usage::SegmentSpaceUsage;
|
||||||
use crate::store::StoreReader;
|
use crate::store::StoreReader;
|
||||||
use crate::termdict::TermDictionary;
|
use crate::termdict::TermDictionary;
|
||||||
|
use crate::CompositeFile;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use fail::fail_point;
|
use fail::fail_point;
|
||||||
|
|||||||
@@ -539,7 +539,7 @@ impl Directory for MmapDirectory {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
// There are more tests in directory/mod.rs
|
// There are more tests in directory/lib.rs
|
||||||
// The following tests are specific to the MmapDirectory
|
// The following tests are specific to the MmapDirectory
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -148,13 +148,13 @@ fn value_to_u64(value: &Value) -> u64 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::CompositeFile;
|
|
||||||
use crate::directory::{Directory, RAMDirectory, WritePtr};
|
use crate::directory::{Directory, RAMDirectory, WritePtr};
|
||||||
use crate::fastfield::FastFieldReader;
|
use crate::fastfield::FastFieldReader;
|
||||||
use crate::schema::Document;
|
use crate::schema::Document;
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use crate::schema::FAST;
|
use crate::schema::FAST;
|
||||||
|
use crate::CompositeFile;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use rand::prelude::SliceRandom;
|
use rand::prelude::SliceRandom;
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ use super::FastValue;
|
|||||||
use crate::common::bitpacker::BitUnpacker;
|
use crate::common::bitpacker::BitUnpacker;
|
||||||
use crate::common::compute_num_bits;
|
use crate::common::compute_num_bits;
|
||||||
use crate::common::BinarySerializable;
|
use crate::common::BinarySerializable;
|
||||||
use crate::common::CompositeFile;
|
|
||||||
use crate::directory::ReadOnlySource;
|
use crate::directory::ReadOnlySource;
|
||||||
use crate::directory::{Directory, RAMDirectory, WritePtr};
|
use crate::directory::{Directory, RAMDirectory, WritePtr};
|
||||||
use crate::fastfield::{FastFieldSerializer, FastFieldsWriter};
|
use crate::fastfield::{FastFieldSerializer, FastFieldsWriter};
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use crate::schema::FAST;
|
use crate::schema::FAST;
|
||||||
|
use crate::CompositeFile;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use owning_ref::OwningRef;
|
use owning_ref::OwningRef;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::common::CompositeFile;
|
|
||||||
use crate::fastfield::BytesFastFieldReader;
|
use crate::fastfield::BytesFastFieldReader;
|
||||||
use crate::fastfield::MultiValueIntFastFieldReader;
|
use crate::fastfield::MultiValueIntFastFieldReader;
|
||||||
use crate::fastfield::{FastFieldNotAvailableError, FastFieldReader};
|
use crate::fastfield::{FastFieldNotAvailableError, FastFieldReader};
|
||||||
use crate::schema::{Cardinality, Field, FieldType, Schema};
|
use crate::schema::{Cardinality, Field, FieldType, Schema};
|
||||||
use crate::space_usage::PerFieldSpaceUsage;
|
use crate::space_usage::PerFieldSpaceUsage;
|
||||||
|
use crate::CompositeFile;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
use crate::common::bitpacker::BitPacker;
|
use crate::common::bitpacker::BitPacker;
|
||||||
use crate::common::compute_num_bits;
|
use crate::common::compute_num_bits;
|
||||||
use crate::common::BinarySerializable;
|
use crate::common::BinarySerializable;
|
||||||
use crate::common::CompositeWrite;
|
|
||||||
use crate::common::CountingWriter;
|
use crate::common::CountingWriter;
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::WritePtr;
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
|
use crate::CompositeWrite;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|
||||||
/// `FastFieldSerializer` is in charge of serializing
|
/// `FastFieldSerializer` is in charge of serializing
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::common::CompositeWrite;
|
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::WritePtr;
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
|
use crate::CompositeWrite;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
|
|||||||
32
src/lib.rs
32
src/lib.rs
@@ -1,4 +1,5 @@
|
|||||||
#![doc(html_logo_url = "http://fulmicoton.com/tantivy-logo/tantivy-logo.png")]
|
#![doc(html_logo_url = "http://fulmicoton.com/tantivy-logo/tantivy-logo.png")]
|
||||||
|
#![recursion_limit = "100"]
|
||||||
#![cfg_attr(all(feature = "unstable", test), feature(test))]
|
#![cfg_attr(all(feature = "unstable", test), feature(test))]
|
||||||
#![cfg_attr(feature = "cargo-clippy", allow(clippy::module_inception))]
|
#![cfg_attr(feature = "cargo-clippy", allow(clippy::module_inception))]
|
||||||
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
|
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
|
||||||
@@ -101,9 +102,6 @@
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde_derive;
|
extern crate serde_derive;
|
||||||
|
|
||||||
#[cfg_attr(test, macro_use)]
|
|
||||||
extern crate serde_json;
|
|
||||||
|
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate log;
|
extern crate log;
|
||||||
|
|
||||||
@@ -120,6 +118,9 @@ mod functional_test;
|
|||||||
#[macro_use]
|
#[macro_use]
|
||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
|
mod composite_file;
|
||||||
|
pub(crate) use composite_file::{CompositeFile, CompositeWrite};
|
||||||
|
|
||||||
pub use crate::error::TantivyError;
|
pub use crate::error::TantivyError;
|
||||||
|
|
||||||
#[deprecated(since = "0.7.0", note = "please use `tantivy::TantivyError` instead")]
|
#[deprecated(since = "0.7.0", note = "please use `tantivy::TantivyError` instead")]
|
||||||
@@ -132,22 +133,22 @@ pub type Result<T> = std::result::Result<T, error::TantivyError>;
|
|||||||
/// Tantivy DateTime
|
/// Tantivy DateTime
|
||||||
pub type DateTime = chrono::DateTime<chrono::Utc>;
|
pub type DateTime = chrono::DateTime<chrono::Utc>;
|
||||||
|
|
||||||
mod common;
|
pub use tantivy_common as common;
|
||||||
|
pub use tantivy_schema as schema;
|
||||||
|
pub use tantivy_tokenizer as tokenizer;
|
||||||
|
|
||||||
mod core;
|
mod core;
|
||||||
mod indexer;
|
mod indexer;
|
||||||
|
|
||||||
#[allow(unused_doc_comments)]
|
|
||||||
mod error;
|
|
||||||
pub mod tokenizer;
|
|
||||||
|
|
||||||
pub mod collector;
|
pub mod collector;
|
||||||
pub mod directory;
|
pub mod directory;
|
||||||
|
#[allow(unused_doc_comments)]
|
||||||
|
mod error;
|
||||||
pub mod fastfield;
|
pub mod fastfield;
|
||||||
pub mod fieldnorm;
|
pub mod fieldnorm;
|
||||||
pub(crate) mod positions;
|
pub(crate) mod positions;
|
||||||
pub mod postings;
|
pub mod postings;
|
||||||
pub mod query;
|
pub mod query;
|
||||||
pub mod schema;
|
|
||||||
pub mod space_usage;
|
pub mod space_usage;
|
||||||
pub mod store;
|
pub mod store;
|
||||||
pub mod termdict;
|
pub mod termdict;
|
||||||
@@ -251,7 +252,6 @@ mod tests {
|
|||||||
use crate::Postings;
|
use crate::Postings;
|
||||||
use crate::ReloadPolicy;
|
use crate::ReloadPolicy;
|
||||||
use rand::distributions::Bernoulli;
|
use rand::distributions::Bernoulli;
|
||||||
use rand::distributions::Uniform;
|
|
||||||
use rand::rngs::StdRng;
|
use rand::rngs::StdRng;
|
||||||
use rand::{Rng, SeedableRng};
|
use rand::{Rng, SeedableRng};
|
||||||
|
|
||||||
@@ -268,14 +268,6 @@ mod tests {
|
|||||||
(a - b).abs() < 0.0005 * (a + b).abs()
|
(a - b).abs() < 0.0005 * (a + b).abs()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn generate_nonunique_unsorted(max_value: u32, n_elems: usize) -> Vec<u32> {
|
|
||||||
let seed: [u8; 32] = [1; 32];
|
|
||||||
StdRng::from_seed(seed)
|
|
||||||
.sample_iter(&Uniform::new(0u32, max_value))
|
|
||||||
.take(n_elems)
|
|
||||||
.collect::<Vec<u32>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn sample_with_seed(n: u32, ratio: f64, seed_val: u8) -> Vec<u32> {
|
pub fn sample_with_seed(n: u32, ratio: f64, seed_val: u8) -> Vec<u32> {
|
||||||
StdRng::from_seed([seed_val; 32])
|
StdRng::from_seed([seed_val; 32])
|
||||||
.sample_iter(&Bernoulli::new(ratio).unwrap())
|
.sample_iter(&Bernoulli::new(ratio).unwrap())
|
||||||
@@ -285,10 +277,6 @@ mod tests {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn sample(n: u32, ratio: f64) -> Vec<u32> {
|
|
||||||
sample_with_seed(n, ratio, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
fn test_indexing() {
|
fn test_indexing() {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use super::TermInfo;
|
use super::TermInfo;
|
||||||
|
use crate::common::CountingWriter;
|
||||||
use crate::common::{BinarySerializable, VInt};
|
use crate::common::{BinarySerializable, VInt};
|
||||||
use crate::common::{CompositeWrite, CountingWriter};
|
|
||||||
use crate::core::Segment;
|
use crate::core::Segment;
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::WritePtr;
|
||||||
use crate::positions::PositionSerializer;
|
use crate::positions::PositionSerializer;
|
||||||
@@ -10,6 +10,7 @@ use crate::postings::USE_SKIP_INFO_LIMIT;
|
|||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use crate::schema::{Field, FieldEntry, FieldType};
|
use crate::schema::{Field, FieldEntry, FieldType};
|
||||||
use crate::termdict::{TermDictionaryBuilder, TermOrdinal};
|
use crate::termdict::{TermDictionaryBuilder, TermOrdinal};
|
||||||
|
use crate::CompositeWrite;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ impl BinarySerializable for TermInfo {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::TermInfo;
|
use super::TermInfo;
|
||||||
use crate::common::test::fixed_size_test;
|
use crate::common::fixed_size_test;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_fixed_size() {
|
fn test_fixed_size() {
|
||||||
|
|||||||
@@ -216,7 +216,6 @@ mod tests {
|
|||||||
assert!(!docset.advance());
|
assert!(!docset.advance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
|
|||||||
@@ -1,14 +1,12 @@
|
|||||||
use crate::error::TantivyError::InvalidArgument;
|
use crate::error::TantivyError::InvalidArgument;
|
||||||
use crate::query::{AutomatonWeight, Query, Weight};
|
use crate::query::{AutomatonWeight, Query, Weight};
|
||||||
use crate::schema::Term;
|
use crate::schema::Term;
|
||||||
use crate::termdict::WrappedDFA;
|
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
use levenshtein_automata::{Distance, LevenshteinAutomatonBuilder, DFA};
|
use levenshtein_automata::{LevenshteinAutomatonBuilder, DFA};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use derive_builder::Builder;
|
|
||||||
|
|
||||||
/// A range of Levenshtein distances that we will build DFAs for our terms
|
/// A range of Levenshtein distances that we will build DFAs for our terms
|
||||||
/// The computation is exponential, so best keep it to low single digits
|
/// The computation is exponential, so best keep it to low single digits
|
||||||
@@ -26,38 +24,6 @@ static LEV_BUILDER: Lazy<HashMap<(u8, bool), LevenshteinAutomatonBuilder>> = Laz
|
|||||||
lev_builder_cache
|
lev_builder_cache
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
#[derive(Builder, Default, Clone, Debug)]
|
|
||||||
pub struct FuzzyConfiguration {
|
|
||||||
/// How many changes are we going to allow
|
|
||||||
pub distance: u8,
|
|
||||||
/// Should a transposition cost 1 or 2?
|
|
||||||
#[builder(default)]
|
|
||||||
pub transposition_cost_one: bool,
|
|
||||||
#[builder(default)]
|
|
||||||
pub prefix: bool,
|
|
||||||
/// If true, only the term with a levenshtein of exactly `distance` will match.
|
|
||||||
/// If false, terms at a distance `<=` to `distance` will match.
|
|
||||||
#[builder(default)]
|
|
||||||
pub exact_distance: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_dfa(fuzzy_configuration: &FuzzyConfiguration, term_text: &str) -> Result<DFA> {
|
|
||||||
let automaton_builder = LEV_BUILDER
|
|
||||||
.get(&(fuzzy_configuration.distance, fuzzy_configuration.transposition_cost_one))
|
|
||||||
.ok_or_else(|| {
|
|
||||||
InvalidArgument(format!(
|
|
||||||
"Levenshtein distance of {} is not allowed. Choose a value in the {:?} range",
|
|
||||||
fuzzy_configuration.distance, VALID_LEVENSHTEIN_DISTANCE_RANGE
|
|
||||||
))
|
|
||||||
})?;
|
|
||||||
if fuzzy_configuration.prefix {
|
|
||||||
Ok(automaton_builder.build_prefix_dfa(term_text))
|
|
||||||
} else {
|
|
||||||
Ok(automaton_builder.build_dfa(term_text))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A Fuzzy Query matches all of the documents
|
/// A Fuzzy Query matches all of the documents
|
||||||
/// containing a specific term that is within
|
/// containing a specific term that is within
|
||||||
/// Levenshtein distance
|
/// Levenshtein distance
|
||||||
@@ -75,19 +41,32 @@ fn build_dfa(fuzzy_configuration: &FuzzyConfiguration, term_text: &str) -> Resul
|
|||||||
/// let index = Index::create_in_ram(schema);
|
/// let index = Index::create_in_ram(schema);
|
||||||
/// {
|
/// {
|
||||||
/// let mut index_writer = index.writer(3_000_000)?;
|
/// let mut index_writer = index.writer(3_000_000)?;
|
||||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"));
|
/// index_writer.add_document(doc!(
|
||||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"));
|
/// title => "The Name of the Wind",
|
||||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"));
|
/// ));
|
||||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"));
|
/// index_writer.add_document(doc!(
|
||||||
|
/// title => "The Diary of Muadib",
|
||||||
|
/// ));
|
||||||
|
/// index_writer.add_document(doc!(
|
||||||
|
/// title => "A Dairy Cow",
|
||||||
|
/// ));
|
||||||
|
/// index_writer.add_document(doc!(
|
||||||
|
/// title => "The Diary of a Young Girl",
|
||||||
|
/// ));
|
||||||
/// index_writer.commit().unwrap();
|
/// index_writer.commit().unwrap();
|
||||||
/// }
|
/// }
|
||||||
/// let reader = index.reader()?;
|
/// let reader = index.reader()?;
|
||||||
/// let searcher = reader.searcher();
|
/// let searcher = reader.searcher();
|
||||||
/// let term = Term::from_field_text(title, "Diary");
|
///
|
||||||
/// let query = FuzzyTermQuery::new(term, 1, true);
|
/// {
|
||||||
/// let (top_docs, count) = searcher.search(&query, &(TopDocs::with_limit(2), Count)).unwrap();
|
///
|
||||||
/// assert_eq!(count, 2);
|
/// let term = Term::from_field_text(title, "Diary");
|
||||||
/// assert_eq!(top_docs.len(), 2);
|
/// let query = FuzzyTermQuery::new(term, 1, true);
|
||||||
|
/// let (top_docs, count) = searcher.search(&query, &(TopDocs::with_limit(2), Count)).unwrap();
|
||||||
|
/// assert_eq!(count, 2);
|
||||||
|
/// assert_eq!(top_docs.len(), 2);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
@@ -95,58 +74,54 @@ fn build_dfa(fuzzy_configuration: &FuzzyConfiguration, term_text: &str) -> Resul
|
|||||||
pub struct FuzzyTermQuery {
|
pub struct FuzzyTermQuery {
|
||||||
/// What term are we searching
|
/// What term are we searching
|
||||||
term: Term,
|
term: Term,
|
||||||
configuration: FuzzyConfiguration
|
/// How many changes are we going to allow
|
||||||
|
distance: u8,
|
||||||
|
/// Should a transposition cost 1 or 2?
|
||||||
|
transposition_cost_one: bool,
|
||||||
|
///
|
||||||
|
prefix: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FuzzyTermQuery {
|
impl FuzzyTermQuery {
|
||||||
pub fn new_from_configuration(term: Term, configuration: FuzzyConfiguration) -> FuzzyTermQuery {
|
|
||||||
FuzzyTermQuery {
|
|
||||||
term,
|
|
||||||
configuration
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new Fuzzy Query
|
/// Creates a new Fuzzy Query
|
||||||
pub fn new(term: Term, distance: u8, transposition_cost_one: bool) -> FuzzyTermQuery {
|
pub fn new(term: Term, distance: u8, transposition_cost_one: bool) -> FuzzyTermQuery {
|
||||||
FuzzyTermQuery {
|
FuzzyTermQuery {
|
||||||
term,
|
term,
|
||||||
configuration: FuzzyConfiguration {
|
distance,
|
||||||
distance,
|
transposition_cost_one,
|
||||||
transposition_cost_one,
|
prefix: false,
|
||||||
prefix: false,
|
}
|
||||||
exact_distance: false
|
}
|
||||||
|
|
||||||
|
/// Creates a new Fuzzy Query that treats transpositions as cost one rather than two
|
||||||
|
pub fn new_prefix(term: Term, distance: u8, transposition_cost_one: bool) -> FuzzyTermQuery {
|
||||||
|
FuzzyTermQuery {
|
||||||
|
term,
|
||||||
|
distance,
|
||||||
|
transposition_cost_one,
|
||||||
|
prefix: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn specialized_weight(&self) -> Result<AutomatonWeight<DFA>> {
|
||||||
|
// LEV_BUILDER is a HashMap, whose `get` method returns an Option
|
||||||
|
match LEV_BUILDER.get(&(self.distance, false)) {
|
||||||
|
// Unwrap the option and build the Ok(AutomatonWeight)
|
||||||
|
Some(automaton_builder) => {
|
||||||
|
let automaton = automaton_builder.build_dfa(self.term.text());
|
||||||
|
Ok(AutomatonWeight::new(self.term.field(), automaton))
|
||||||
}
|
}
|
||||||
|
None => Err(InvalidArgument(format!(
|
||||||
|
"Levenshtein distance of {} is not allowed. Choose a value in the {:?} range",
|
||||||
|
self.distance, VALID_LEVENSHTEIN_DISTANCE_RANGE
|
||||||
|
))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Query for FuzzyTermQuery {
|
impl Query for FuzzyTermQuery {
|
||||||
fn weight(&self, _searcher: &Searcher, _scoring_enabled: bool) -> Result<Box<dyn Weight>> {
|
fn weight(&self, _searcher: &Searcher, _scoring_enabled: bool) -> Result<Box<dyn Weight>> {
|
||||||
let dfa = build_dfa(&self.configuration, self.term.text())?;
|
Ok(Box::new(self.specialized_weight()?))
|
||||||
// TODO optimize for distance = 0 and possibly prefix
|
|
||||||
if self.configuration.exact_distance {
|
|
||||||
let target_distance = self.configuration.distance;
|
|
||||||
let wrapped_dfa = WrappedDFA {
|
|
||||||
dfa,
|
|
||||||
condition: move |distance: Distance| distance == Distance::Exact(target_distance),
|
|
||||||
};
|
|
||||||
Ok(Box::new(AutomatonWeight::new(
|
|
||||||
self.term.field(),
|
|
||||||
wrapped_dfa,
|
|
||||||
)))
|
|
||||||
} else {
|
|
||||||
let wrapped_dfa = WrappedDFA {
|
|
||||||
dfa,
|
|
||||||
condition: move |distance: Distance| match distance {
|
|
||||||
Distance::Exact(_) => true,
|
|
||||||
Distance::AtLeast(_) => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
Ok(Box::new(AutomatonWeight::new(
|
|
||||||
self.term.field(),
|
|
||||||
wrapped_dfa,
|
|
||||||
)))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +134,6 @@ mod test {
|
|||||||
use crate::tests::assert_nearly_equals;
|
use crate::tests::assert_nearly_equals;
|
||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::Term;
|
use crate::Term;
|
||||||
use super::FuzzyConfigurationBuilder;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_fuzzy_term() {
|
pub fn test_fuzzy_term() {
|
||||||
@@ -181,6 +155,7 @@ mod test {
|
|||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
{
|
{
|
||||||
let term = Term::from_field_text(country_field, "japon");
|
let term = Term::from_field_text(country_field, "japon");
|
||||||
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new(term, 1, true);
|
let fuzzy_query = FuzzyTermQuery::new(term, 1, true);
|
||||||
let top_docs = searcher
|
let top_docs = searcher
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
||||||
@@ -189,73 +164,5 @@ mod test {
|
|||||||
let (score, _) = top_docs[0];
|
let (score, _) = top_docs[0];
|
||||||
assert_nearly_equals(1f32, score);
|
assert_nearly_equals(1f32, score);
|
||||||
}
|
}
|
||||||
{
|
|
||||||
let term = Term::from_field_text(country_field, "japon");
|
|
||||||
let fuzzy_conf = FuzzyConfigurationBuilder::default()
|
|
||||||
.distance(2)
|
|
||||||
.exact_distance(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new_from_configuration(term, fuzzy_conf);
|
|
||||||
let top_docs = searcher
|
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
|
||||||
.unwrap();
|
|
||||||
assert!(top_docs.is_empty());
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let term = Term::from_field_text(country_field, "japon");
|
|
||||||
let fuzzy_conf = FuzzyConfigurationBuilder::default()
|
|
||||||
.distance(1)
|
|
||||||
.exact_distance(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new_from_configuration(term, fuzzy_conf);
|
|
||||||
let top_docs = searcher
|
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(top_docs.len(), 1);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let term = Term::from_field_text(country_field, "jpp");
|
|
||||||
let fuzzy_conf = FuzzyConfigurationBuilder::default()
|
|
||||||
.distance(1)
|
|
||||||
.prefix(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new_from_configuration(term, fuzzy_conf);
|
|
||||||
let top_docs = searcher
|
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(top_docs.len(), 1);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let term = Term::from_field_text(country_field, "jpaan");
|
|
||||||
let fuzzy_conf = FuzzyConfigurationBuilder::default()
|
|
||||||
.distance(1)
|
|
||||||
.exact_distance(true)
|
|
||||||
.transposition_cost_one(true)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new_from_configuration(term, fuzzy_conf);
|
|
||||||
let top_docs = searcher
|
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(top_docs.len(), 1);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let term = Term::from_field_text(country_field, "jpaan");
|
|
||||||
let fuzzy_conf = FuzzyConfigurationBuilder::default()
|
|
||||||
.distance(2)
|
|
||||||
.exact_distance(true)
|
|
||||||
.transposition_cost_one(false)
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
let fuzzy_query = FuzzyTermQuery::new_from_configuration(term, fuzzy_conf);
|
|
||||||
let top_docs = searcher
|
|
||||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(top_docs.len(), 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ mod exclude;
|
|||||||
mod explanation;
|
mod explanation;
|
||||||
mod fuzzy_query;
|
mod fuzzy_query;
|
||||||
mod intersection;
|
mod intersection;
|
||||||
|
mod occur;
|
||||||
mod phrase_query;
|
mod phrase_query;
|
||||||
mod query;
|
mod query;
|
||||||
mod query_parser;
|
mod query_parser;
|
||||||
@@ -40,8 +41,9 @@ pub use self::boolean_query::BooleanQuery;
|
|||||||
pub use self::empty_query::{EmptyQuery, EmptyScorer, EmptyWeight};
|
pub use self::empty_query::{EmptyQuery, EmptyScorer, EmptyWeight};
|
||||||
pub use self::exclude::Exclude;
|
pub use self::exclude::Exclude;
|
||||||
pub use self::explanation::Explanation;
|
pub use self::explanation::Explanation;
|
||||||
pub use self::fuzzy_query::{FuzzyTermQuery, FuzzyConfiguration, FuzzyConfigurationBuilder};
|
pub use self::fuzzy_query::FuzzyTermQuery;
|
||||||
pub use self::intersection::intersect_scorers;
|
pub use self::intersection::intersect_scorers;
|
||||||
|
pub use self::occur::Occur;
|
||||||
pub use self::phrase_query::PhraseQuery;
|
pub use self::phrase_query::PhraseQuery;
|
||||||
pub use self::query::Query;
|
pub use self::query::Query;
|
||||||
pub use self::query_parser::QueryParser;
|
pub use self::query_parser::QueryParser;
|
||||||
@@ -53,7 +55,6 @@ pub use self::scorer::ConstScorer;
|
|||||||
pub use self::scorer::Scorer;
|
pub use self::scorer::Scorer;
|
||||||
pub use self::term_query::TermQuery;
|
pub use self::term_query::TermQuery;
|
||||||
pub use self::weight::Weight;
|
pub use self::weight::Weight;
|
||||||
pub use tantivy_query_grammar::Occur;
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|||||||
@@ -25,24 +25,24 @@ impl Occur {
|
|||||||
Occur::MustNot => '-',
|
Occur::MustNot => '-',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Compose two occur values.
|
/// Compose two occur values.
|
||||||
pub fn compose(left: Occur, right: Occur) -> Occur {
|
pub fn compose_occur(left: Occur, right: Occur) -> Occur {
|
||||||
match left {
|
match left {
|
||||||
Occur::Should => right,
|
Occur::Should => right,
|
||||||
Occur::Must => {
|
Occur::Must => {
|
||||||
if right == Occur::MustNot {
|
if right == Occur::MustNot {
|
||||||
Occur::MustNot
|
Occur::MustNot
|
||||||
} else {
|
} else {
|
||||||
Occur::Must
|
Occur::Must
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Occur::MustNot => {
|
}
|
||||||
if right == Occur::MustNot {
|
Occur::MustNot => {
|
||||||
Occur::Must
|
if right == Occur::MustNot {
|
||||||
} else {
|
Occur::Must
|
||||||
Occur::MustNot
|
} else {
|
||||||
}
|
Occur::MustNot
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
mod query_grammar;
|
||||||
mod query_parser;
|
mod query_parser;
|
||||||
|
mod user_input_ast;
|
||||||
|
|
||||||
pub mod logical_ast;
|
pub mod logical_ast;
|
||||||
pub use self::query_parser::QueryParser;
|
pub use self::query_parser::QueryParser;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::user_input_ast::*;
|
use super::user_input_ast::*;
|
||||||
use crate::Occur;
|
use crate::query::occur::Occur;
|
||||||
|
use crate::query::query_parser::user_input_ast::UserInputBound;
|
||||||
use combine::char::*;
|
use combine::char::*;
|
||||||
use combine::error::StreamError;
|
use combine::error::StreamError;
|
||||||
use combine::stream::StreamErrorFor;
|
use combine::stream::StreamErrorFor;
|
||||||
@@ -1,5 +1,9 @@
|
|||||||
use super::logical_ast::*;
|
use super::logical_ast::*;
|
||||||
|
use super::query_grammar::parse_to_ast;
|
||||||
|
use super::user_input_ast::*;
|
||||||
use crate::core::Index;
|
use crate::core::Index;
|
||||||
|
use crate::query::occur::compose_occur;
|
||||||
|
use crate::query::query_parser::logical_ast::LogicalAST;
|
||||||
use crate::query::AllQuery;
|
use crate::query::AllQuery;
|
||||||
use crate::query::BooleanQuery;
|
use crate::query::BooleanQuery;
|
||||||
use crate::query::EmptyQuery;
|
use crate::query::EmptyQuery;
|
||||||
@@ -12,11 +16,11 @@ use crate::schema::IndexRecordOption;
|
|||||||
use crate::schema::{Field, Schema};
|
use crate::schema::{Field, Schema};
|
||||||
use crate::schema::{FieldType, Term};
|
use crate::schema::{FieldType, Term};
|
||||||
use crate::tokenizer::TokenizerManager;
|
use crate::tokenizer::TokenizerManager;
|
||||||
|
use combine::Parser;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::num::{ParseFloatError, ParseIntError};
|
use std::num::{ParseFloatError, ParseIntError};
|
||||||
use std::ops::Bound;
|
use std::ops::Bound;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use tantivy_query_grammar::{UserInputAST, UserInputBound, UserInputLeaf};
|
|
||||||
|
|
||||||
/// Possible error that may happen when parsing a query.
|
/// Possible error that may happen when parsing a query.
|
||||||
#[derive(Debug, PartialEq, Eq, Fail)]
|
#[derive(Debug, PartialEq, Eq, Fail)]
|
||||||
@@ -218,8 +222,9 @@ impl QueryParser {
|
|||||||
|
|
||||||
/// Parse the user query into an AST.
|
/// Parse the user query into an AST.
|
||||||
fn parse_query_to_logical_ast(&self, query: &str) -> Result<LogicalAST, QueryParserError> {
|
fn parse_query_to_logical_ast(&self, query: &str) -> Result<LogicalAST, QueryParserError> {
|
||||||
let user_input_ast =
|
let (user_input_ast, _remaining) = parse_to_ast()
|
||||||
tantivy_query_grammar::parse_query(query).map_err(|_| QueryParserError::SyntaxError)?;
|
.parse(query)
|
||||||
|
.map_err(|_| QueryParserError::SyntaxError)?;
|
||||||
self.compute_logical_ast(user_input_ast)
|
self.compute_logical_ast(user_input_ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -394,7 +399,7 @@ impl QueryParser {
|
|||||||
let mut logical_sub_queries: Vec<(Occur, LogicalAST)> = Vec::new();
|
let mut logical_sub_queries: Vec<(Occur, LogicalAST)> = Vec::new();
|
||||||
for sub_query in sub_queries {
|
for sub_query in sub_queries {
|
||||||
let (occur, sub_ast) = self.compute_logical_ast_with_occur(sub_query)?;
|
let (occur, sub_ast) = self.compute_logical_ast_with_occur(sub_query)?;
|
||||||
let new_occur = Occur::compose(default_occur, occur);
|
let new_occur = compose_occur(default_occur, occur);
|
||||||
logical_sub_queries.push((new_occur, sub_ast));
|
logical_sub_queries.push((new_occur, sub_ast));
|
||||||
}
|
}
|
||||||
Ok((Occur::Should, LogicalAST::Clause(logical_sub_queries)))
|
Ok((Occur::Should, LogicalAST::Clause(logical_sub_queries)))
|
||||||
@@ -402,7 +407,7 @@ impl QueryParser {
|
|||||||
UserInputAST::Unary(left_occur, subquery) => {
|
UserInputAST::Unary(left_occur, subquery) => {
|
||||||
let (right_occur, logical_sub_queries) =
|
let (right_occur, logical_sub_queries) =
|
||||||
self.compute_logical_ast_with_occur(*subquery)?;
|
self.compute_logical_ast_with_occur(*subquery)?;
|
||||||
Ok((Occur::compose(left_occur, right_occur), logical_sub_queries))
|
Ok((compose_occur(left_occur, right_occur), logical_sub_queries))
|
||||||
}
|
}
|
||||||
UserInputAST::Leaf(leaf) => {
|
UserInputAST::Leaf(leaf) => {
|
||||||
let result_ast = self.compute_logical_ast_from_leaf(*leaf)?;
|
let result_ast = self.compute_logical_ast_from_leaf(*leaf)?;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
use crate::Occur;
|
use crate::query::Occur;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
pub enum UserInputLeaf {
|
pub enum UserInputLeaf {
|
||||||
@@ -31,43 +31,14 @@ mod termdict;
|
|||||||
pub use self::merger::TermMerger;
|
pub use self::merger::TermMerger;
|
||||||
pub use self::streamer::{TermStreamer, TermStreamerBuilder};
|
pub use self::streamer::{TermStreamer, TermStreamerBuilder};
|
||||||
pub use self::termdict::{TermDictionary, TermDictionaryBuilder};
|
pub use self::termdict::{TermDictionary, TermDictionaryBuilder};
|
||||||
use levenshtein_automata::{Distance, DFA, SINK_STATE};
|
|
||||||
use tantivy_fst::Automaton;
|
|
||||||
|
|
||||||
pub(crate) struct WrappedDFA<Cond> {
|
|
||||||
pub dfa: DFA,
|
|
||||||
pub condition: Cond,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<Cond: Fn(Distance) -> bool> Automaton for WrappedDFA<Cond> {
|
|
||||||
type State = u32;
|
|
||||||
|
|
||||||
fn start(&self) -> Self::State {
|
|
||||||
self.dfa.initial_state()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_match(&self, state: &Self::State) -> bool {
|
|
||||||
let distance = self.dfa.distance(*state);
|
|
||||||
(self.condition)(distance)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn can_match(&self, state: &Self::State) -> bool {
|
|
||||||
*state != SINK_STATE
|
|
||||||
}
|
|
||||||
|
|
||||||
fn accept(&self, state: &Self::State, byte: u8) -> Self::State {
|
|
||||||
self.dfa.transition(*state, byte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{TermDictionary, TermDictionaryBuilder, TermStreamer, WrappedDFA};
|
use super::{TermDictionary, TermDictionaryBuilder, TermStreamer};
|
||||||
use crate::core::Index;
|
use crate::core::Index;
|
||||||
use crate::directory::{Directory, RAMDirectory, ReadOnlySource};
|
use crate::directory::{Directory, RAMDirectory, ReadOnlySource};
|
||||||
use crate::postings::TermInfo;
|
use crate::postings::TermInfo;
|
||||||
use crate::schema::{Document, FieldType, Schema, TEXT};
|
use crate::schema::{Document, FieldType, Schema, TEXT};
|
||||||
use levenshtein_automata::Distance;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
@@ -452,14 +423,9 @@ mod tests {
|
|||||||
|
|
||||||
// We can now build an entire dfa.
|
// We can now build an entire dfa.
|
||||||
let lev_automaton_builder = LevenshteinAutomatonBuilder::new(2, true);
|
let lev_automaton_builder = LevenshteinAutomatonBuilder::new(2, true);
|
||||||
let wrapped_dfa = WrappedDFA {
|
let automaton = lev_automaton_builder.build_dfa("Spaen");
|
||||||
dfa: lev_automaton_builder.build_dfa("Spaen"),
|
|
||||||
condition: |distance| match distance {
|
let mut range = term_dict.search(automaton).into_stream();
|
||||||
Distance::Exact(_) => true,
|
|
||||||
Distance::AtLeast(_) => false,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
let mut range = term_dict.search(wrapped_dfa).into_stream();
|
|
||||||
|
|
||||||
// get the first finding
|
// get the first finding
|
||||||
assert!(range.advance());
|
assert!(range.advance());
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_term_info_block() {
|
fn test_term_info_block() {
|
||||||
common::test::fixed_size_test::<TermInfoBlockMeta>();
|
common::fixed_size_test::<TermInfoBlockMeta>();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "incremental-search"
|
name = "tantivy-common"
|
||||||
version = "0.11.0"
|
version = "0.1.0"
|
||||||
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
derive_builder = "0.7"
|
byteorder = "*"
|
||||||
tantivy = {path = ".."}
|
chrono = "*"
|
||||||
@@ -2,7 +2,7 @@ use byteorder::{ByteOrder, LittleEndian, WriteBytesExt};
|
|||||||
use std::io;
|
use std::io;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
pub(crate) struct BitPacker {
|
pub struct BitPacker {
|
||||||
mini_buffer: u64,
|
mini_buffer: u64,
|
||||||
mini_buffer_written: usize,
|
mini_buffer_written: usize,
|
||||||
}
|
}
|
||||||
@@ -2,7 +2,7 @@ use std::fmt;
|
|||||||
use std::u64;
|
use std::u64;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
pub(crate) struct TinySet(u64);
|
pub struct TinySet(u64);
|
||||||
|
|
||||||
impl fmt::Debug for TinySet {
|
impl fmt::Debug for TinySet {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
@@ -179,7 +179,7 @@ impl BitSet {
|
|||||||
///
|
///
|
||||||
/// Reminder: the tiny set with the bucket `bucket`, represents the
|
/// Reminder: the tiny set with the bucket `bucket`, represents the
|
||||||
/// elements from `bucket * 64` to `(bucket+1) * 64`.
|
/// elements from `bucket * 64` to `(bucket+1) * 64`.
|
||||||
pub(crate) fn first_non_empty_bucket(&self, bucket: u32) -> Option<u32> {
|
pub fn first_non_empty_bucket(&self, bucket: u32) -> Option<u32> {
|
||||||
self.tinysets[bucket as usize..]
|
self.tinysets[bucket as usize..]
|
||||||
.iter()
|
.iter()
|
||||||
.cloned()
|
.cloned()
|
||||||
@@ -194,7 +194,7 @@ impl BitSet {
|
|||||||
/// Returns the tiny bitset representing the
|
/// Returns the tiny bitset representing the
|
||||||
/// the set restricted to the number range from
|
/// the set restricted to the number range from
|
||||||
/// `bucket * 64` to `(bucket + 1) * 64`.
|
/// `bucket * 64` to `(bucket + 1) * 64`.
|
||||||
pub(crate) fn tinyset(&self, bucket: u32) -> TinySet {
|
pub fn tinyset(&self, bucket: u32) -> TinySet {
|
||||||
self.tinysets[bucket as usize]
|
self.tinysets[bucket as usize]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -204,12 +204,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::BitSet;
|
use super::BitSet;
|
||||||
use super::TinySet;
|
use super::TinySet;
|
||||||
use crate::docset::DocSet;
|
use std::collections::{BTreeSet, HashSet};
|
||||||
use crate::query::BitSetDocSet;
|
|
||||||
use crate::tests;
|
|
||||||
use crate::tests::generate_nonunique_unsorted;
|
|
||||||
use std::collections::BTreeSet;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_tiny_set() {
|
fn test_tiny_set() {
|
||||||
@@ -264,26 +259,19 @@ mod tests {
|
|||||||
test_against_hashset(&[62u32, 63u32], 64);
|
test_against_hashset(&[62u32, 63u32], 64);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
// #[test]
|
||||||
fn test_bitset_large() {
|
// fn test_bitset_clear() {
|
||||||
let arr = generate_nonunique_unsorted(100_000, 5_000);
|
// let mut bitset = BitSet::with_max_value(1_000);
|
||||||
let mut btreeset: BTreeSet<u32> = BTreeSet::new();
|
// let els = tests::sample(1_000, 0.01f64);
|
||||||
let mut bitset = BitSet::with_max_value(100_000);
|
// for &el in &els {
|
||||||
for el in arr {
|
// bitset.insert(el);
|
||||||
btreeset.insert(el);
|
// }
|
||||||
bitset.insert(el);
|
// assert!(els.iter().all(|el| bitset.contains(*el)));
|
||||||
}
|
// bitset.clear();
|
||||||
for i in 0..100_000 {
|
// for el in 0u32..1000u32 {
|
||||||
assert_eq!(btreeset.contains(&i), bitset.contains(i));
|
// assert!(!bitset.contains(el));
|
||||||
}
|
// }
|
||||||
assert_eq!(btreeset.len(), bitset.len());
|
// }
|
||||||
let mut bitset_docset = BitSetDocSet::from(bitset);
|
|
||||||
for el in btreeset.into_iter() {
|
|
||||||
bitset_docset.advance();
|
|
||||||
assert_eq!(bitset_docset.doc(), el);
|
|
||||||
}
|
|
||||||
assert!(!bitset_docset.advance());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bitset_num_buckets() {
|
fn test_bitset_num_buckets() {
|
||||||
@@ -339,19 +327,6 @@ mod tests {
|
|||||||
assert_eq!(bitset.len(), 3);
|
assert_eq!(bitset.len(), 3);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_bitset_clear() {
|
|
||||||
let mut bitset = BitSet::with_max_value(1_000);
|
|
||||||
let els = tests::sample(1_000, 0.01f64);
|
|
||||||
for &el in &els {
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
assert!(els.iter().all(|el| bitset.contains(*el)));
|
|
||||||
bitset.clear();
|
|
||||||
for el in 0u32..1000u32 {
|
|
||||||
assert!(!bitset.contains(el));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
235
tantivy-common/src/composite_file.rs
Normal file
235
tantivy-common/src/composite_file.rs
Normal file
@@ -0,0 +1,235 @@
|
|||||||
|
use crate::common::BinarySerializable;
|
||||||
|
use crate::common::CountingWriter;
|
||||||
|
use crate::common::VInt;
|
||||||
|
use crate::directory::ReadOnlySource;
|
||||||
|
use crate::directory::WritePtr;
|
||||||
|
use crate::schema::Field;
|
||||||
|
use crate::space_usage::FieldUsage;
|
||||||
|
use crate::space_usage::PerFieldSpaceUsage;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::io::{self, Read};
|
||||||
|
|
||||||
|
#[derive(Eq, PartialEq, Hash, Copy, Ord, PartialOrd, Clone, Debug)]
|
||||||
|
pub struct FileAddr {
|
||||||
|
field: Field,
|
||||||
|
idx: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FileAddr {
|
||||||
|
fn new(field: Field, idx: usize) -> FileAddr {
|
||||||
|
FileAddr { field, idx }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BinarySerializable for FileAddr {
|
||||||
|
fn serialize<W: Write>(&self, writer: &mut W) -> io::Result<()> {
|
||||||
|
self.field.serialize(writer)?;
|
||||||
|
VInt(self.idx as u64).serialize(writer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deserialize<R: Read>(reader: &mut R) -> io::Result<Self> {
|
||||||
|
let field = Field::deserialize(reader)?;
|
||||||
|
let idx = VInt::deserialize(reader)?.0 as usize;
|
||||||
|
Ok(FileAddr { field, idx })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A `CompositeWrite` is used to write a `CompositeFile`.
|
||||||
|
pub struct CompositeWrite<W = WritePtr> {
|
||||||
|
write: CountingWriter<W>,
|
||||||
|
offsets: HashMap<FileAddr, u64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: Write> CompositeWrite<W> {
|
||||||
|
/// Crate a new API writer that writes a composite file
|
||||||
|
/// in a given write.
|
||||||
|
pub fn wrap(w: W) -> CompositeWrite<W> {
|
||||||
|
CompositeWrite {
|
||||||
|
write: CountingWriter::wrap(w),
|
||||||
|
offsets: HashMap::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start writing a new field.
|
||||||
|
pub fn for_field(&mut self, field: Field) -> &mut CountingWriter<W> {
|
||||||
|
self.for_field_with_idx(field, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Start writing a new field.
|
||||||
|
pub fn for_field_with_idx(&mut self, field: Field, idx: usize) -> &mut CountingWriter<W> {
|
||||||
|
let offset = self.write.written_bytes();
|
||||||
|
let file_addr = FileAddr::new(field, idx);
|
||||||
|
assert!(!self.offsets.contains_key(&file_addr));
|
||||||
|
self.offsets.insert(file_addr, offset);
|
||||||
|
&mut self.write
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Close the composite file
|
||||||
|
///
|
||||||
|
/// An index of the different field offsets
|
||||||
|
/// will be written as a footer.
|
||||||
|
pub fn close(mut self) -> io::Result<()> {
|
||||||
|
let footer_offset = self.write.written_bytes();
|
||||||
|
VInt(self.offsets.len() as u64).serialize(&mut self.write)?;
|
||||||
|
|
||||||
|
let mut offset_fields: Vec<_> = self
|
||||||
|
.offsets
|
||||||
|
.iter()
|
||||||
|
.map(|(file_addr, offset)| (*offset, *file_addr))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
offset_fields.sort();
|
||||||
|
|
||||||
|
let mut prev_offset = 0;
|
||||||
|
for (offset, file_addr) in offset_fields {
|
||||||
|
VInt((offset - prev_offset) as u64).serialize(&mut self.write)?;
|
||||||
|
file_addr.serialize(&mut self.write)?;
|
||||||
|
prev_offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
let footer_len = (self.write.written_bytes() - footer_offset) as u32;
|
||||||
|
footer_len.serialize(&mut self.write)?;
|
||||||
|
self.write.flush()?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A composite file is an abstraction to store a
|
||||||
|
/// file partitioned by field.
|
||||||
|
///
|
||||||
|
/// The file needs to be written field by field.
|
||||||
|
/// A footer describes the start and stop offsets
|
||||||
|
/// for each field.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct CompositeFile {
|
||||||
|
data: ReadOnlySource,
|
||||||
|
offsets_index: HashMap<FileAddr, (usize, usize)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CompositeFile {
|
||||||
|
/// Opens a composite file stored in a given
|
||||||
|
/// `ReadOnlySource`.
|
||||||
|
pub fn open(data: &ReadOnlySource) -> io::Result<CompositeFile> {
|
||||||
|
let end = data.len();
|
||||||
|
let footer_len_data = data.slice_from(end - 4);
|
||||||
|
let footer_len = u32::deserialize(&mut footer_len_data.as_slice())? as usize;
|
||||||
|
let footer_start = end - 4 - footer_len;
|
||||||
|
let footer_data = data.slice(footer_start, footer_start + footer_len);
|
||||||
|
let mut footer_buffer = footer_data.as_slice();
|
||||||
|
let num_fields = VInt::deserialize(&mut footer_buffer)?.0 as usize;
|
||||||
|
|
||||||
|
let mut file_addrs = vec![];
|
||||||
|
let mut offsets = vec![];
|
||||||
|
|
||||||
|
let mut field_index = HashMap::new();
|
||||||
|
|
||||||
|
let mut offset = 0;
|
||||||
|
for _ in 0..num_fields {
|
||||||
|
offset += VInt::deserialize(&mut footer_buffer)?.0 as usize;
|
||||||
|
let file_addr = FileAddr::deserialize(&mut footer_buffer)?;
|
||||||
|
offsets.push(offset);
|
||||||
|
file_addrs.push(file_addr);
|
||||||
|
}
|
||||||
|
offsets.push(footer_start);
|
||||||
|
for i in 0..num_fields {
|
||||||
|
let file_addr = file_addrs[i];
|
||||||
|
let start_offset = offsets[i];
|
||||||
|
let end_offset = offsets[i + 1];
|
||||||
|
field_index.insert(file_addr, (start_offset, end_offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(CompositeFile {
|
||||||
|
data: data.slice_to(footer_start),
|
||||||
|
offsets_index: field_index,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a composite file that stores
|
||||||
|
/// no fields.
|
||||||
|
pub fn empty() -> CompositeFile {
|
||||||
|
CompositeFile {
|
||||||
|
offsets_index: HashMap::new(),
|
||||||
|
data: ReadOnlySource::empty(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `ReadOnlySource` associated
|
||||||
|
/// to a given `Field` and stored in a `CompositeFile`.
|
||||||
|
pub fn open_read(&self, field: Field) -> Option<ReadOnlySource> {
|
||||||
|
self.open_read_with_idx(field, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the `ReadOnlySource` associated
|
||||||
|
/// to a given `Field` and stored in a `CompositeFile`.
|
||||||
|
pub fn open_read_with_idx(&self, field: Field, idx: usize) -> Option<ReadOnlySource> {
|
||||||
|
self.offsets_index
|
||||||
|
.get(&FileAddr { field, idx })
|
||||||
|
.map(|&(from, to)| self.data.slice(from, to))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn space_usage(&self) -> PerFieldSpaceUsage {
|
||||||
|
let mut fields = HashMap::new();
|
||||||
|
for (&field_addr, &(start, end)) in self.offsets_index.iter() {
|
||||||
|
fields
|
||||||
|
.entry(field_addr.field)
|
||||||
|
.or_insert_with(|| FieldUsage::empty(field_addr.field))
|
||||||
|
.add_field_idx(field_addr.idx, end - start);
|
||||||
|
}
|
||||||
|
PerFieldSpaceUsage::new(fields)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
|
||||||
|
use super::{CompositeFile, CompositeWrite};
|
||||||
|
use crate::common::BinarySerializable;
|
||||||
|
use crate::common::VInt;
|
||||||
|
use crate::directory::{Directory, RAMDirectory};
|
||||||
|
use crate::schema::Field;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_composite_file() {
|
||||||
|
let path = Path::new("test_path");
|
||||||
|
let mut directory = RAMDirectory::create();
|
||||||
|
{
|
||||||
|
let w = directory.open_write(path).unwrap();
|
||||||
|
let mut composite_write = CompositeWrite::wrap(w);
|
||||||
|
{
|
||||||
|
let mut write_0 = composite_write.for_field(Field(0u32));
|
||||||
|
VInt(32431123u64).serialize(&mut write_0).unwrap();
|
||||||
|
write_0.flush().unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let mut write_4 = composite_write.for_field(Field(4u32));
|
||||||
|
VInt(2).serialize(&mut write_4).unwrap();
|
||||||
|
write_4.flush().unwrap();
|
||||||
|
}
|
||||||
|
composite_write.close().unwrap();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let r = directory.open_read(path).unwrap();
|
||||||
|
let composite_file = CompositeFile::open(&r).unwrap();
|
||||||
|
{
|
||||||
|
let file0 = composite_file.open_read(Field(0u32)).unwrap();
|
||||||
|
let mut file0_buf = file0.as_slice();
|
||||||
|
let payload_0 = VInt::deserialize(&mut file0_buf).unwrap().0;
|
||||||
|
assert_eq!(file0_buf.len(), 0);
|
||||||
|
assert_eq!(payload_0, 32431123u64);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
let file4 = composite_file.open_read(Field(4u32)).unwrap();
|
||||||
|
let mut file4_buf = file4.as_slice();
|
||||||
|
let payload_4 = VInt::deserialize(&mut file4_buf).unwrap().0;
|
||||||
|
assert_eq!(file4_buf.len(), 0);
|
||||||
|
assert_eq!(payload_4, 2u64);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,18 +1,18 @@
|
|||||||
pub mod bitpacker;
|
pub mod bitpacker;
|
||||||
mod bitset;
|
mod bitset;
|
||||||
mod composite_file;
|
|
||||||
mod counting_writer;
|
mod counting_writer;
|
||||||
mod serialize;
|
mod serialize;
|
||||||
mod vint;
|
mod vint;
|
||||||
|
|
||||||
pub use self::bitset::BitSet;
|
pub use self::bitset::BitSet;
|
||||||
pub(crate) use self::bitset::TinySet;
|
pub use self::bitset::TinySet;
|
||||||
pub(crate) use self::composite_file::{CompositeFile, CompositeWrite};
|
|
||||||
pub use self::counting_writer::CountingWriter;
|
pub use self::counting_writer::CountingWriter;
|
||||||
pub use self::serialize::{BinarySerializable, FixedSize};
|
pub use self::serialize::{BinarySerializable, FixedSize};
|
||||||
pub use self::vint::{read_u32_vint, serialize_vint_u32, write_u32_vint, VInt};
|
pub use self::vint::{read_u32_vint, serialize_vint_u32, write_u32_vint, VInt};
|
||||||
pub use byteorder::LittleEndian as Endianness;
|
pub use byteorder::LittleEndian as Endianness;
|
||||||
|
|
||||||
|
pub type DateTime = chrono::DateTime<chrono::Utc>;
|
||||||
|
|
||||||
/// Segment's max doc must be `< MAX_DOC_LIMIT`.
|
/// Segment's max doc must be `< MAX_DOC_LIMIT`.
|
||||||
///
|
///
|
||||||
/// We do not allow segments with more than
|
/// We do not allow segments with more than
|
||||||
@@ -42,7 +42,7 @@ pub const MAX_DOC_LIMIT: u32 = 1 << 31;
|
|||||||
/// a very large range of values. Even in this case, it results
|
/// a very large range of values. Even in this case, it results
|
||||||
/// in an extra cost of at most 12% compared to the optimal
|
/// in an extra cost of at most 12% compared to the optimal
|
||||||
/// number of bits.
|
/// number of bits.
|
||||||
pub(crate) fn compute_num_bits(n: u64) -> u8 {
|
pub fn compute_num_bits(n: u64) -> u8 {
|
||||||
let amplitude = (64u32 - n.leading_zeros()) as u8;
|
let amplitude = (64u32 - n.leading_zeros()) as u8;
|
||||||
if amplitude <= 64 - 8 {
|
if amplitude <= 64 - 8 {
|
||||||
amplitude
|
amplitude
|
||||||
@@ -51,7 +51,7 @@ pub(crate) fn compute_num_bits(n: u64) -> u8 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_power_of_2(n: usize) -> bool {
|
pub fn is_power_of_2(n: usize) -> bool {
|
||||||
(n > 0) && (n & (n - 1) == 0)
|
(n > 0) && (n & (n - 1) == 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,10 +131,12 @@ pub fn u64_to_f64(val: u64) -> f64 {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use self::serialize::fixed_size_test;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
|
||||||
pub use super::serialize::test::fixed_size_test;
|
use super::fixed_size_test;
|
||||||
use super::{compute_num_bits, f64_to_u64, i64_to_u64, u64_to_f64, u64_to_i64};
|
use super::{compute_num_bits, f64_to_u64, i64_to_u64, u64_to_f64, u64_to_i64};
|
||||||
use std::f64;
|
use std::f64;
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::common::Endianness;
|
use crate::Endianness;
|
||||||
use crate::common::VInt;
|
use crate::VInt;
|
||||||
use byteorder::{ReadBytesExt, WriteBytesExt};
|
use byteorder::{ReadBytesExt, WriteBytesExt};
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -145,17 +145,17 @@ impl BinarySerializable for String {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn fixed_size_test<O: BinarySerializable + FixedSize + Default>() {
|
||||||
|
let mut buffer = Vec::new();
|
||||||
|
O::default().serialize(&mut buffer).unwrap();
|
||||||
|
assert_eq!(buffer.len(), O::SIZE_IN_BYTES);
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod test {
|
mod test {
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::VInt;
|
use crate::VInt;
|
||||||
|
|
||||||
pub fn fixed_size_test<O: BinarySerializable + FixedSize + Default>() {
|
|
||||||
let mut buffer = Vec::new();
|
|
||||||
O::default().serialize(&mut buffer).unwrap();
|
|
||||||
assert_eq!(buffer.len(), O::SIZE_IN_BYTES);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn serialize_test<T: BinarySerializable + Eq>(v: T) -> usize {
|
fn serialize_test<T: BinarySerializable + Eq>(v: T) -> usize {
|
||||||
let mut buffer: Vec<u8> = Vec::new();
|
let mut buffer: Vec<u8> = Vec::new();
|
||||||
@@ -171,7 +171,7 @@ mod tests {
|
|||||||
|
|
||||||
use super::serialize_vint_u32;
|
use super::serialize_vint_u32;
|
||||||
use super::VInt;
|
use super::VInt;
|
||||||
use crate::common::BinarySerializable;
|
use crate::BinarySerializable;
|
||||||
use byteorder::{ByteOrder, LittleEndian};
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
|
||||||
fn aux_test_vint(val: u64) {
|
fn aux_test_vint(val: u64) {
|
||||||
33
tantivy-schema/Cargo.toml
Normal file
33
tantivy-schema/Cargo.toml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
[package]
|
||||||
|
name = "tantivy-schema"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
base64 = "0.10.0"
|
||||||
|
byteorder = "1.0"
|
||||||
|
once_cell = "0.2"
|
||||||
|
regex = "1.0"
|
||||||
|
serde = "1.0"
|
||||||
|
serde_derive = "1.0"
|
||||||
|
serde_json = "1.0"
|
||||||
|
num_cpus = "1.2"
|
||||||
|
itertools = "0.8"
|
||||||
|
notify = {version="4", optional=true}
|
||||||
|
crossbeam = "0.7"
|
||||||
|
owning_ref = "0.4"
|
||||||
|
stable_deref_trait = "1.0.0"
|
||||||
|
downcast-rs = { version="1.0" }
|
||||||
|
census = "0.2"
|
||||||
|
failure = "0.1"
|
||||||
|
fail = "0.3"
|
||||||
|
scoped-pool = "1.0"
|
||||||
|
tantivy-common = {path="../tantivy-common"}
|
||||||
|
chrono = "*"
|
||||||
|
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
matches = "0.1.8"
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::common::BinarySerializable;
|
|
||||||
use crate::common::VInt;
|
|
||||||
use crate::DateTime;
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
use tantivy_common::BinarySerializable;
|
||||||
|
use tantivy_common::DateTime;
|
||||||
|
use tantivy_common::VInt;
|
||||||
|
|
||||||
/// Tantivy's Document is the object that can
|
/// Tantivy's Document is the object that can
|
||||||
/// be indexed and then searched for.
|
/// be indexed and then searched for.
|
||||||
@@ -168,7 +169,7 @@ impl BinarySerializable for Document {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::schema::*;
|
use crate::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_doc() {
|
fn test_doc() {
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
use crate::common::BinarySerializable;
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@@ -8,6 +7,7 @@ use std::fmt::{self, Debug, Display, Formatter};
|
|||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
use std::str;
|
use std::str;
|
||||||
use std::string::FromUtf8Error;
|
use std::string::FromUtf8Error;
|
||||||
|
use tantivy_common::BinarySerializable;
|
||||||
|
|
||||||
const SLASH_BYTE: u8 = b'/';
|
const SLASH_BYTE: u8 = b'/';
|
||||||
const ESCAPE_BYTE: u8 = b'\\';
|
const ESCAPE_BYTE: u8 = b'\\';
|
||||||
@@ -59,7 +59,7 @@ impl Facet {
|
|||||||
&self.0
|
&self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_encoded_string(facet_string: String) -> Facet {
|
pub fn from_encoded_string(facet_string: String) -> Facet {
|
||||||
Facet(facet_string)
|
Facet(facet_string)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,7 +104,7 @@ impl Facet {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Accessor for the inner buffer of the `Facet`.
|
/// Accessor for the inner buffer of the `Facet`.
|
||||||
pub(crate) fn set_facet_str(&mut self, facet_str: &str) {
|
pub fn set_facet_str(&mut self, facet_str: &str) {
|
||||||
self.0.clear();
|
self.0.clear();
|
||||||
self.0.push_str(facet_str);
|
self.0.push_str(facet_str);
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::common::BinarySerializable;
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use tantivy_common::BinarySerializable;
|
||||||
|
|
||||||
/// `Field` is actually a `u8` identifying a `Field`
|
/// `Field` is actually a `u8` identifying a `Field`
|
||||||
/// The schema is in charge of holding mapping between field names
|
/// The schema is in charge of holding mapping between field names
|
||||||
@@ -1,7 +1,8 @@
|
|||||||
use crate::schema::IntOptions;
|
use serde_derive::*;
|
||||||
use crate::schema::TextOptions;
|
|
||||||
|
|
||||||
use crate::schema::FieldType;
|
use crate::FieldType;
|
||||||
|
use crate::IntOptions;
|
||||||
|
use crate::TextOptions;
|
||||||
use serde::de::{self, MapAccess, Visitor};
|
use serde::de::{self, MapAccess, Visitor};
|
||||||
use serde::ser::SerializeStruct;
|
use serde::ser::SerializeStruct;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
@@ -265,7 +266,7 @@ impl<'de> Deserialize<'de> for FieldEntry {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::schema::TEXT;
|
use crate::TEXT;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1,11 +1,10 @@
|
|||||||
use base64::decode;
|
use base64::decode;
|
||||||
|
|
||||||
use crate::schema::{IntOptions, TextOptions};
|
use crate::Facet;
|
||||||
|
use crate::IndexRecordOption;
|
||||||
use crate::schema::Facet;
|
use crate::TextFieldIndexing;
|
||||||
use crate::schema::IndexRecordOption;
|
use crate::Value;
|
||||||
use crate::schema::TextFieldIndexing;
|
use crate::{IntOptions, TextOptions};
|
||||||
use crate::schema::Value;
|
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
|
||||||
/// Possible error that may occur while parsing a field value
|
/// Possible error that may occur while parsing a field value
|
||||||
@@ -183,8 +182,9 @@ impl FieldType {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::FieldType;
|
use super::FieldType;
|
||||||
use crate::schema::field_type::ValueParsingError;
|
use crate::field_type::ValueParsingError;
|
||||||
use crate::schema::Value;
|
use crate::Value;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_bytes_value_from_json() {
|
fn test_bytes_value_from_json() {
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use crate::common::BinarySerializable;
|
use crate::Field;
|
||||||
use crate::schema::Field;
|
use crate::Value;
|
||||||
use crate::schema::Value;
|
//use serde::Deserialize;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
use tantivy_common::BinarySerializable;
|
||||||
|
|
||||||
/// `FieldValue` holds together a `Field` and its `Value`.
|
/// `FieldValue` holds together a `Field` and its `Value`.
|
||||||
#[derive(Debug, Clone, Ord, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Ord, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::schema::IntOptions;
|
use crate::IntOptions;
|
||||||
use crate::schema::TextOptions;
|
use crate::TextOptions;
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// `IndexRecordOption` describes an amount information associated
|
/// `IndexRecordOption` describes an amount information associated
|
||||||
/// to a given indexed field.
|
/// to a given indexed field.
|
||||||
///
|
///
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::schema::flags::{FastFlag, IndexedFlag, SchemaFlagList, StoredFlag};
|
use crate::flags::{FastFlag, IndexedFlag, SchemaFlagList, StoredFlag};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
/// Express whether a field is single-value or multi-valued.
|
/// Express whether a field is single-value or multi-valued.
|
||||||
@@ -26,7 +26,7 @@ directory.
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```
|
```
|
||||||
use tantivy::schema::*;
|
use tantivy_schema::*;
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
let title_options = TextOptions::default()
|
let title_options = TextOptions::default()
|
||||||
.set_stored()
|
.set_stored()
|
||||||
@@ -59,7 +59,7 @@ when [`searcher.doc(doc_address)`](../struct.Searcher.html#method.doc) is called
|
|||||||
### Example
|
### Example
|
||||||
|
|
||||||
```
|
```
|
||||||
use tantivy::schema::*;
|
use tantivy_schema::*;
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
let num_stars_options = IntOptions::default()
|
let num_stars_options = IntOptions::default()
|
||||||
.set_stored()
|
.set_stored()
|
||||||
@@ -93,7 +93,7 @@ using the `|` operator.
|
|||||||
For instance, a schema containing the two fields defined in the example above could be rewritten :
|
For instance, a schema containing the two fields defined in the example above could be rewritten :
|
||||||
|
|
||||||
```
|
```
|
||||||
use tantivy::schema::*;
|
use tantivy_schema::*;
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
schema_builder.add_u64_field("num_stars", INDEXED | STORED);
|
schema_builder.add_u64_field("num_stars", INDEXED | STORED);
|
||||||
schema_builder.add_text_field("title", TEXT | STORED);
|
schema_builder.add_text_field("title", TEXT | STORED);
|
||||||
@@ -126,7 +126,6 @@ pub use self::schema::{Schema, SchemaBuilder};
|
|||||||
pub use self::value::Value;
|
pub use self::value::Value;
|
||||||
|
|
||||||
pub use self::facet::Facet;
|
pub use self::facet::Facet;
|
||||||
pub(crate) use self::facet::FACET_SEP_BYTE;
|
|
||||||
|
|
||||||
pub use self::document::Document;
|
pub use self::document::Document;
|
||||||
pub use self::field::Field;
|
pub use self::field::Field;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::schema::Value;
|
use crate::Value;
|
||||||
|
use serde_derive::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Internal representation of a document used for JSON
|
/// Internal representation of a document used for JSON
|
||||||
@@ -1,14 +1,14 @@
|
|||||||
use crate::schema::field_type::ValueParsingError;
|
|
||||||
use std::collections::BTreeMap;
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::schema::field_type::ValueParsingError;
|
||||||
|
use failure::Fail;
|
||||||
use serde::de::{SeqAccess, Visitor};
|
use serde::de::{SeqAccess, Visitor};
|
||||||
use serde::ser::SerializeSeq;
|
use serde::ser::SerializeSeq;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use serde_json::{self, Map as JsonObject, Value as JsonValue};
|
use serde_json::{self, Map as JsonObject, Value as JsonValue};
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Tantivy has a very strict schema.
|
/// Tantivy has a very strict schema.
|
||||||
/// You need to specify in advance whether a field is indexed or not,
|
/// You need to specify in advance whether a field is indexed or not,
|
||||||
@@ -21,7 +21,7 @@ use std::fmt;
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tantivy::schema::*;
|
/// use tantivy_schema::*;
|
||||||
///
|
///
|
||||||
/// let mut schema_builder = Schema::builder();
|
/// let mut schema_builder = Schema::builder();
|
||||||
/// let id_field = schema_builder.add_text_field("id", STRING);
|
/// let id_field = schema_builder.add_text_field("id", STRING);
|
||||||
@@ -208,7 +208,7 @@ impl Eq for InnerSchema {}
|
|||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tantivy::schema::*;
|
/// use tantivy_schema::*;
|
||||||
///
|
///
|
||||||
/// let mut schema_builder = Schema::builder();
|
/// let mut schema_builder = Schema::builder();
|
||||||
/// let id_field = schema_builder.add_text_field("id", STRING);
|
/// let id_field = schema_builder.add_text_field("id", STRING);
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use super::Field;
|
use crate::Facet;
|
||||||
use crate::common;
|
use crate::Field;
|
||||||
use crate::schema::Facet;
|
|
||||||
use crate::DateTime;
|
|
||||||
use byteorder::{BigEndian, ByteOrder};
|
use byteorder::{BigEndian, ByteOrder};
|
||||||
use std::str;
|
use std::str;
|
||||||
|
use tantivy_common as common;
|
||||||
|
use tantivy_common::DateTime;
|
||||||
|
|
||||||
/// Size (in bytes) of the buffer of a int field.
|
/// Size (in bytes) of the buffer of a int field.
|
||||||
const INT_TERM_LEN: usize = 4 + 8;
|
const INT_TERM_LEN: usize = 4 + 8;
|
||||||
@@ -94,7 +94,7 @@ impl Term {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new Term for a given field.
|
/// Creates a new Term for a given field.
|
||||||
pub(crate) fn for_field(field: Field) -> Term {
|
pub fn for_field(field: Field) -> Term {
|
||||||
let mut term = Term(Vec::with_capacity(100));
|
let mut term = Term(Vec::with_capacity(100));
|
||||||
term.set_field(field);
|
term.set_field(field);
|
||||||
term
|
term
|
||||||
@@ -134,7 +134,7 @@ impl Term {
|
|||||||
self.0.extend(bytes);
|
self.0.extend(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn from_field_bytes(field: Field, bytes: &[u8]) -> Term {
|
pub fn from_field_bytes(field: Field, bytes: &[u8]) -> Term {
|
||||||
let mut term = Term::for_field(field);
|
let mut term = Term::for_field(field);
|
||||||
term.set_bytes(bytes);
|
term.set_bytes(bytes);
|
||||||
term
|
term
|
||||||
@@ -236,7 +236,7 @@ impl fmt::Debug for Term {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::schema::*;
|
use crate::{Schema, Term, STRING};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_term() {
|
pub fn test_term() {
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::schema::flags::SchemaFlagList;
|
use crate::flags::SchemaFlagList;
|
||||||
use crate::schema::flags::StoredFlag;
|
use crate::flags::StoredFlag;
|
||||||
use crate::schema::IndexRecordOption;
|
use crate::IndexRecordOption;
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
@@ -151,7 +152,7 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::schema::*;
|
use crate::{FieldType, IndexRecordOption, Schema, STORED, TEXT};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_field_options() {
|
fn test_field_options() {
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
use crate::schema::Facet;
|
use crate::Facet;
|
||||||
use crate::DateTime;
|
use chrono;
|
||||||
use serde::de::Visitor;
|
use serde::de::Visitor;
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
use std::{cmp::Ordering, fmt};
|
use std::{cmp::Ordering, fmt};
|
||||||
|
|
||||||
|
pub(crate) type DateTime = chrono::DateTime<chrono::Utc>;
|
||||||
|
|
||||||
/// Value represents the value of a any field.
|
/// Value represents the value of a any field.
|
||||||
/// It is an enum over all over all of the possible field type.
|
/// It is an enum over all over all of the possible field type.
|
||||||
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
#[derive(Debug, Clone, PartialEq, PartialOrd)]
|
||||||
@@ -218,11 +220,11 @@ impl From<Vec<u8>> for Value {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mod binary_serialize {
|
mod binary_serialize {
|
||||||
use super::Value;
|
use crate::Facet;
|
||||||
use crate::common::{f64_to_u64, u64_to_f64, BinarySerializable};
|
use crate::Value;
|
||||||
use crate::schema::Facet;
|
|
||||||
use chrono::{TimeZone, Utc};
|
use chrono::{TimeZone, Utc};
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
use tantivy_common::{f64_to_u64, u64_to_f64, BinarySerializable};
|
||||||
|
|
||||||
const TEXT_CODE: u8 = 0;
|
const TEXT_CODE: u8 = 0;
|
||||||
const U64_CODE: u8 = 1;
|
const U64_CODE: u8 = 1;
|
||||||
13
tantivy-tokenizer/Cargo.toml
Normal file
13
tantivy-tokenizer/Cargo.toml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
[package]
|
||||||
|
name = "tantivy-tokenizer"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||||
|
edition = "2018"
|
||||||
|
workspace = ".."
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
fnv = "*"
|
||||||
|
rust-stemmers = "*"
|
||||||
|
serde = "*"
|
||||||
|
serde_derive = "*"
|
||||||
|
tantivy-schema = {path="../tantivy-schema"}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy_tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//!
|
//!
|
||||||
@@ -64,14 +64,6 @@ impl<TailTokenStream> TokenStream for AlphaNumOnlyFilterStream<TailTokenStream>
|
|||||||
where
|
where
|
||||||
TailTokenStream: TokenStream,
|
TailTokenStream: TokenStream,
|
||||||
{
|
{
|
||||||
fn token(&self) -> &Token {
|
|
||||||
self.tail.token()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn token_mut(&mut self) -> &mut Token {
|
|
||||||
self.tail.token_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn advance(&mut self) -> bool {
|
fn advance(&mut self) -> bool {
|
||||||
while self.tail.advance() {
|
while self.tail.advance() {
|
||||||
if self.predicate(self.tail.token()) {
|
if self.predicate(self.tail.token()) {
|
||||||
@@ -81,4 +73,12 @@ where
|
|||||||
|
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn token(&self) -> &Token {
|
||||||
|
self.tail.token()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn token_mut(&mut self) -> &mut Token {
|
||||||
|
self.tail.token_mut()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1558,11 +1558,11 @@ fn to_ascii(text: &mut String, output: &mut String) {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::to_ascii;
|
use super::to_ascii;
|
||||||
use crate::tokenizer::AsciiFoldingFilter;
|
use crate::AsciiFoldingFilter;
|
||||||
use crate::tokenizer::RawTokenizer;
|
use crate::RawTokenizer;
|
||||||
use crate::tokenizer::SimpleTokenizer;
|
use crate::SimpleTokenizer;
|
||||||
use crate::tokenizer::TokenStream;
|
use crate::TokenStream;
|
||||||
use crate::tokenizer::Tokenizer;
|
use crate::Tokenizer;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use super::{Token, TokenStream, Tokenizer};
|
use super::{Token, TokenStream, Tokenizer};
|
||||||
use crate::schema::FACET_SEP_BYTE;
|
use crate::FACET_SEP_BYTE;
|
||||||
|
|
||||||
/// The `FacetTokenizer` process a `Facet` binary representation
|
/// The `FacetTokenizer` process a `Facet` binary representation
|
||||||
/// and emits a token for all of its parent.
|
/// and emits a token for all of its parent.
|
||||||
@@ -83,8 +83,8 @@ impl<'a> TokenStream for FacetTokenStream<'a> {
|
|||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::FacetTokenizer;
|
use super::FacetTokenizer;
|
||||||
use crate::schema::Facet;
|
|
||||||
use crate::tokenizer::{Token, TokenStream, Tokenizer};
|
use crate::tokenizer::{Token, TokenStream, Tokenizer};
|
||||||
|
use tantivy_schema::Facet;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_facet_tokenizer() {
|
fn test_facet_tokenizer() {
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
//! each of your fields :
|
//! each of your fields :
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::schema::*;
|
//! use tantivy_schema::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! let mut schema_builder = Schema::builder();
|
//! let mut schema_builder = Schema::builder();
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
//! For instance, the `en_stem` is defined as follows.
|
//! For instance, the `en_stem` is defined as follows.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy_tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! let en_stem = SimpleTokenizer
|
//! let en_stem = SimpleTokenizer
|
||||||
@@ -78,8 +78,8 @@
|
|||||||
//! register it with a name in your index's [`TokenizerManager`](./struct.TokenizerManager.html).
|
//! register it with a name in your index's [`TokenizerManager`](./struct.TokenizerManager.html).
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # use tantivy::schema::Schema;
|
//! # use tantivy_schema::Schema;
|
||||||
//! # use tantivy::tokenizer::*;
|
//! # use tantivy_tokenizer::*;
|
||||||
//! # use tantivy::Index;
|
//! # use tantivy::Index;
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # let custom_en_tokenizer = SimpleTokenizer;
|
//! # let custom_en_tokenizer = SimpleTokenizer;
|
||||||
@@ -98,8 +98,8 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::schema::{Schema, IndexRecordOption, TextOptions, TextFieldIndexing};
|
//! use tantivy_schema::{Schema, IndexRecordOption, TextOptions, TextFieldIndexing};
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy_tokenizer::*;
|
||||||
//! use tantivy::Index;
|
//! use tantivy::Index;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
@@ -150,8 +150,10 @@ pub use self::simple_tokenizer::SimpleTokenizer;
|
|||||||
pub use self::stemmer::{Language, Stemmer};
|
pub use self::stemmer::{Language, Stemmer};
|
||||||
pub use self::stop_word_filter::StopWordFilter;
|
pub use self::stop_word_filter::StopWordFilter;
|
||||||
pub(crate) use self::token_stream_chain::TokenStreamChain;
|
pub(crate) use self::token_stream_chain::TokenStreamChain;
|
||||||
|
|
||||||
pub use self::tokenizer::BoxedTokenizer;
|
pub use self::tokenizer::BoxedTokenizer;
|
||||||
|
|
||||||
|
pub(crate) const FACET_SEP_BYTE: u8 = 0u8;
|
||||||
pub use self::tokenizer::{Token, TokenFilter, TokenStream, Tokenizer};
|
pub use self::tokenizer::{Token, TokenFilter, TokenStream, Tokenizer};
|
||||||
pub use self::tokenizer_manager::TokenizerManager;
|
pub use self::tokenizer_manager::TokenizerManager;
|
||||||
|
|
||||||
@@ -72,10 +72,10 @@ where
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use crate::tokenizer::LowerCaser;
|
use crate::LowerCaser;
|
||||||
use crate::tokenizer::SimpleTokenizer;
|
use crate::SimpleTokenizer;
|
||||||
use crate::tokenizer::TokenStream;
|
use crate::TokenStream;
|
||||||
use crate::tokenizer::Tokenizer;
|
use crate::Tokenizer;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_to_lower_case() {
|
fn test_to_lower_case() {
|
||||||
@@ -30,7 +30,7 @@ use super::{Token, TokenStream, Tokenizer};
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy_tokenizer::*;
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let tokenizer = NgramTokenizer::new(2, 3, false);
|
/// let tokenizer = NgramTokenizer::new(2, 3, false);
|
||||||
/// let mut stream = tokenizer.token_stream("hello");
|
/// let mut stream = tokenizer.token_stream("hello");
|
||||||
@@ -308,9 +308,9 @@ mod tests {
|
|||||||
use super::CodepointFrontiers;
|
use super::CodepointFrontiers;
|
||||||
use super::NgramTokenizer;
|
use super::NgramTokenizer;
|
||||||
use super::StutteringIterator;
|
use super::StutteringIterator;
|
||||||
use crate::tokenizer::tests::assert_token;
|
use crate::tests::assert_token;
|
||||||
use crate::tokenizer::tokenizer::{TokenStream, Tokenizer};
|
use crate::tokenizer::{TokenStream, Tokenizer};
|
||||||
use crate::tokenizer::Token;
|
use crate::Token;
|
||||||
|
|
||||||
fn test_helper<T: TokenStream>(mut tokenizer: T) -> Vec<Token> {
|
fn test_helper<T: TokenStream>(mut tokenizer: T) -> Vec<Token> {
|
||||||
let mut tokens: Vec<Token> = vec![];
|
let mut tokens: Vec<Token> = vec![];
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy_tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//!
|
//!
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use super::{Token, TokenFilter, TokenStream};
|
use super::{Token, TokenFilter, TokenStream};
|
||||||
use rust_stemmers::{self, Algorithm};
|
use rust_stemmers::{self, Algorithm};
|
||||||
|
use serde_derive::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Available stemmer languages.
|
/// Available stemmer languages.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy_tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! let tokenizer = SimpleTokenizer
|
//! let tokenizer = SimpleTokenizer
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::tokenizer::{Token, TokenStream};
|
use crate::{Token, TokenStream};
|
||||||
|
|
||||||
const POSITION_GAP: usize = 2;
|
const POSITION_GAP: usize = 2;
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::tokenizer::TokenStreamChain;
|
use crate::TokenStreamChain;
|
||||||
/// The tokenizer module contains all of the tools used to process
|
/// The tokenizer module contains all of the tools used to process
|
||||||
/// text in `tantivy`.
|
/// text in `tantivy`.
|
||||||
use std::borrow::{Borrow, BorrowMut};
|
use std::borrow::{Borrow, BorrowMut};
|
||||||
@@ -56,7 +56,7 @@ pub trait Tokenizer<'a>: Sized + Clone {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy_tokenizer::*;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let en_stem = SimpleTokenizer
|
/// let en_stem = SimpleTokenizer
|
||||||
@@ -186,7 +186,7 @@ impl<'b> TokenStream for Box<dyn TokenStream + 'b> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy_tokenizer::*;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let tokenizer = SimpleTokenizer
|
/// let tokenizer = SimpleTokenizer
|
||||||
@@ -227,7 +227,7 @@ pub trait TokenStream {
|
|||||||
/// and `.token()`.
|
/// and `.token()`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # use tantivy::tokenizer::*;
|
/// # use tantivy_tokenizer::*;
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// # let tokenizer = SimpleTokenizer
|
/// # let tokenizer = SimpleTokenizer
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
use crate::tokenizer::stemmer::Language;
|
use crate::stemmer::Language;
|
||||||
use crate::tokenizer::BoxedTokenizer;
|
use crate::BoxedTokenizer;
|
||||||
use crate::tokenizer::LowerCaser;
|
use crate::LowerCaser;
|
||||||
use crate::tokenizer::RawTokenizer;
|
use crate::RawTokenizer;
|
||||||
use crate::tokenizer::RemoveLongFilter;
|
use crate::RemoveLongFilter;
|
||||||
use crate::tokenizer::SimpleTokenizer;
|
use crate::SimpleTokenizer;
|
||||||
use crate::tokenizer::Stemmer;
|
use crate::Stemmer;
|
||||||
use crate::tokenizer::Tokenizer;
|
use crate::Tokenizer;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
@@ -32,11 +32,10 @@ impl TokenizerManager {
|
|||||||
where
|
where
|
||||||
A: Into<BoxedTokenizer>,
|
A: Into<BoxedTokenizer>,
|
||||||
{
|
{
|
||||||
let boxed_tokenizer = tokenizer.into();
|
|
||||||
self.tokenizers
|
self.tokenizers
|
||||||
.write()
|
.write()
|
||||||
.expect("Acquiring the lock should never fail")
|
.expect("Acquiring the lock should never fail")
|
||||||
.insert(tokenizer_name.to_string(), boxed_tokenizer);
|
.insert(tokenizer_name.to_string(), tokenizer.into());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accessing a tokenizer given its name.
|
/// Accessing a tokenizer given its name.
|
||||||
Reference in New Issue
Block a user