mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-04 16:22:55 +00:00
Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9fd23f3abf | ||
|
|
c030990d00 | ||
|
|
4c3941750b | ||
|
|
2ea8e618f2 | ||
|
|
94f27f990b | ||
|
|
349e8aa348 | ||
|
|
cde9b78b8d | ||
|
|
d8894f0bd2 | ||
|
|
7e08e0047b | ||
|
|
1a817f117f | ||
|
|
2ec19b21ae | ||
|
|
141f5a93f7 | ||
|
|
df47d55cd2 | ||
|
|
5e579fd6b7 | ||
|
|
4b9c1dce69 | ||
|
|
d74f71bbef | ||
|
|
5196ca41d8 | ||
|
|
4959e06151 | ||
|
|
c1635c13f6 | ||
|
|
135e0ea2e9 | ||
|
|
f283bfd7ab | ||
|
|
9f74786db2 | ||
|
|
32e5d7a0c7 | ||
|
|
84c615cff1 | ||
|
|
039c0a0863 | ||
|
|
b3b0138b82 | ||
|
|
ea56160cdc | ||
|
|
028b0a749c | ||
|
|
941f06eb9f |
@@ -47,6 +47,7 @@ matrix:
|
|||||||
before_install:
|
before_install:
|
||||||
- set -e
|
- set -e
|
||||||
- rustup self update
|
- rustup self update
|
||||||
|
- rustup component add rustfmt
|
||||||
|
|
||||||
install:
|
install:
|
||||||
- sh ci/install.sh
|
- sh ci/install.sh
|
||||||
@@ -60,6 +61,7 @@ before_script:
|
|||||||
|
|
||||||
script:
|
script:
|
||||||
- bash ci/script.sh
|
- bash ci/script.sh
|
||||||
|
- cargo fmt --all -- --check
|
||||||
|
|
||||||
before_deploy:
|
before_deploy:
|
||||||
- sh ci/before_deploy.sh
|
- sh ci/before_deploy.sh
|
||||||
|
|||||||
15
CHANGELOG.md
15
CHANGELOG.md
@@ -5,7 +5,22 @@ Tantivy 0.11.0
|
|||||||
- Various bugfixes in the query parser.
|
- Various bugfixes in the query parser.
|
||||||
- Better handling of hyphens in query parser. (#609)
|
- Better handling of hyphens in query parser. (#609)
|
||||||
- Better handling of whitespaces.
|
- Better handling of whitespaces.
|
||||||
|
- Closes #498 - add support for Elastic-style unbounded range queries for alphanumeric types eg. "title:>hello", "weight:>=70.5", "height:<200" (@petr-tik)
|
||||||
|
- API change around `Box<BoxableTokenizer>`. See detail in #629
|
||||||
|
- Avoid rebuilding Regex automaton whenever a regex query is reused. #639 (@brainlock)
|
||||||
|
- Add footer with some metadata to index files. #605 (@fdb-hiroshima)
|
||||||
|
|
||||||
|
## How to update?
|
||||||
|
|
||||||
|
- `Box<dyn BoxableTokenizer>` has been replaced by a `BoxedTokenizer` struct.
|
||||||
|
- Regex are now compiled when the `RegexQuery` instance is built. As a result, it can now return
|
||||||
|
an error and handling the `Result` is required.
|
||||||
|
|
||||||
|
|
||||||
|
Tantivy 0.10.2
|
||||||
|
=====================
|
||||||
|
|
||||||
|
- Closes #656. Solving memory leak.
|
||||||
|
|
||||||
Tantivy 0.10.1
|
Tantivy 0.10.1
|
||||||
=====================
|
=====================
|
||||||
|
|||||||
25
Cargo.toml
25
Cargo.toml
@@ -15,8 +15,9 @@ edition = "2018"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.10.0"
|
base64 = "0.10.0"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
once_cell = "0.2"
|
crc32fast = "1.2.0"
|
||||||
regex = "1.0"
|
once_cell = "1.0"
|
||||||
|
regex ={version = "1.3.0", default-features = false, features = ["std"]}
|
||||||
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}
|
||||||
@@ -24,7 +25,6 @@ snap = {version="0.2"}
|
|||||||
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"
|
||||||
@@ -42,6 +42,7 @@ 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"
|
||||||
@@ -62,7 +63,6 @@ rand = "0.7"
|
|||||||
maplit = "1"
|
maplit = "1"
|
||||||
matches = "0.1.8"
|
matches = "0.1.8"
|
||||||
time = "0.1.42"
|
time = "0.1.42"
|
||||||
criterion = "0.2"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
opt-level = 3
|
opt-level = 3
|
||||||
@@ -75,17 +75,20 @@ overflow-checks = true
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["mmap"]
|
default = ["mmap"]
|
||||||
forbench = []
|
|
||||||
mmap = ["atomicwrites", "fs2", "memmap", "notify"]
|
mmap = ["atomicwrites", "fs2", "memmap", "notify"]
|
||||||
lz4-compression = ["lz4"]
|
lz4-compression = ["lz4"]
|
||||||
failpoints = ["fail/failpoints"]
|
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"]
|
||||||
|
|
||||||
[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
|
||||||
@@ -99,15 +102,3 @@ features = ["failpoints"]
|
|||||||
name = "failpoints"
|
name = "failpoints"
|
||||||
path = "tests/failpoints/mod.rs"
|
path = "tests/failpoints/mod.rs"
|
||||||
required-features = ["fail/failpoints"]
|
required-features = ["fail/failpoints"]
|
||||||
|
|
||||||
[profile.bench]
|
|
||||||
lto = true
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "vint"
|
|
||||||
harness = false
|
|
||||||
|
|
||||||
|
|
||||||
[[bench]]
|
|
||||||
name = "fastfield"
|
|
||||||
harness = false
|
|
||||||
3
Makefile
Normal file
3
Makefile
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
test:
|
||||||
|
echo "Run test only... No examples."
|
||||||
|
cargo test --tests --lib
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
|
||||||
use rand::distributions::{Bernoulli, Uniform};
|
|
||||||
use rand::rngs::StdRng;
|
|
||||||
use rand::{Rng, SeedableRng};
|
|
||||||
use tantivy::forbench::bitset::{BitSet, TinySet};
|
|
||||||
use tantivy::query::BitSetDocSet;
|
|
||||||
use tantivy::DocSet;
|
|
||||||
|
|
||||||
fn sample_with_seed(n: u32, ratio: f64, seed_val: u8) -> Vec<u32> {
|
|
||||||
StdRng::from_seed([seed_val; 32])
|
|
||||||
.sample_iter(&Bernoulli::new(ratio).unwrap())
|
|
||||||
.take(n as usize)
|
|
||||||
.enumerate()
|
|
||||||
.filter_map(|(val, keep)| if keep { Some(val as u32) } else { None })
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
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>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_tinyset_pop(criterion: &mut Criterion) {
|
|
||||||
criterion.bench_function("pop_lowest", |b| {
|
|
||||||
b.iter(|| {
|
|
||||||
let mut tinyset = TinySet::singleton(criterion::black_box(31u32));
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
tinyset.pop_lowest();
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_bitset_insert(criterion: &mut Criterion) {
|
|
||||||
criterion.bench_function_over_inputs(
|
|
||||||
"bitset_insert",
|
|
||||||
|bench, (max_value, n_elems)| {
|
|
||||||
let els = generate_nonunique_unsorted(*max_value, *n_elems);
|
|
||||||
bench.iter(move || {
|
|
||||||
let mut bitset = BitSet::with_max_value(1_000_000);
|
|
||||||
for el in els.iter().cloned() {
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
vec![(1_000_000u32, 10_000)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_bitsetdocset_iterate(b: &mut test::Bencher) {
|
|
||||||
let mut bitset = BitSet::with_max_value(1_000_000);
|
|
||||||
for el in sample_with_seed(1_000_000u32, 0.01, 0u8) {
|
|
||||||
bitset.insert(el);
|
|
||||||
}
|
|
||||||
b.iter(|| {
|
|
||||||
let mut docset = BitSetDocSet::from(bitset.clone());
|
|
||||||
while docset.advance() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(
|
|
||||||
benches,
|
|
||||||
bench_tinyset_pop,
|
|
||||||
bench_bitset_insert,
|
|
||||||
bench_bitsetdocset_iterate
|
|
||||||
);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
use criterion::criterion_group;
|
|
||||||
use criterion::criterion_main;
|
|
||||||
use criterion::Criterion;
|
|
||||||
use criterion::ParameterizedBenchmark;
|
|
||||||
use rand::rngs::StdRng;
|
|
||||||
use rand::seq::SliceRandom;
|
|
||||||
use rand::SeedableRng;
|
|
||||||
use tantivy::schema::{Schema, FAST};
|
|
||||||
use tantivy::{doc, DocId, Index};
|
|
||||||
|
|
||||||
const NUM_LOOKUPS: usize = 1_000;
|
|
||||||
|
|
||||||
fn generate_permutation(stride: usize, bit_width: u8) -> Vec<u64> {
|
|
||||||
let mut permutation: Vec<u64> = (0u64..(NUM_LOOKUPS * stride) as u64).collect();
|
|
||||||
permutation.shuffle(&mut StdRng::from_seed([1u8; 32]));
|
|
||||||
permutation.push(1u64 << (bit_width as u64)); //< just to force the bit_width
|
|
||||||
permutation
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_linear_lookup(c: &mut Criterion) {
|
|
||||||
c.bench(
|
|
||||||
"lookup_stride",
|
|
||||||
ParameterizedBenchmark::new(
|
|
||||||
"baseline_vec",
|
|
||||||
|bench, (stride, num_bits)| {
|
|
||||||
let arr = generate_permutation(*stride, *num_bits);
|
|
||||||
bench.iter(move || {
|
|
||||||
let mut a = 0u64;
|
|
||||||
for i in (0..NUM_LOOKUPS / stride).map(|v| v * 7) {
|
|
||||||
a ^= arr[i as usize];
|
|
||||||
}
|
|
||||||
a
|
|
||||||
})
|
|
||||||
},
|
|
||||||
vec![(7, 1), (7, 5), (7, 20)],
|
|
||||||
)
|
|
||||||
.with_function("fastfield", |bench, (stride, num_bits)| {
|
|
||||||
let mut schema_builder = Schema::builder();
|
|
||||||
let val_field = schema_builder.add_u64_field("val", FAST);
|
|
||||||
let schema = schema_builder.build();
|
|
||||||
|
|
||||||
let index = Index::create_in_ram(schema);
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 80_000_000).unwrap();
|
|
||||||
for el in generate_permutation(*stride, *num_bits) {
|
|
||||||
index_writer.add_document(doc!(val_field=>el));
|
|
||||||
}
|
|
||||||
index_writer.commit().unwrap();
|
|
||||||
let reader = index.reader().unwrap();
|
|
||||||
let searcher = reader.searcher();
|
|
||||||
let segment_reader = searcher.segment_reader(0u32);
|
|
||||||
let fast_field_reader = segment_reader.fast_fields().u64(val_field).unwrap();
|
|
||||||
bench.iter(move || {
|
|
||||||
let mut a = 0u64;
|
|
||||||
for i in (0..NUM_LOOKUPS / stride).map(|v| v * 7) {
|
|
||||||
a ^= fast_field_reader.get(i as DocId);
|
|
||||||
}
|
|
||||||
a
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_jumpy_lookup(c: &mut Criterion) {
|
|
||||||
c.bench(
|
|
||||||
"lookup_jumpy",
|
|
||||||
ParameterizedBenchmark::new(
|
|
||||||
"baseline_vec",
|
|
||||||
|bench, (stride, num_bits)| {
|
|
||||||
let arr = generate_permutation(*stride, *num_bits);
|
|
||||||
bench.iter(move || {
|
|
||||||
let mut a = 0u64;
|
|
||||||
for _ in 0..NUM_LOOKUPS {
|
|
||||||
a = arr[a as usize];
|
|
||||||
}
|
|
||||||
a
|
|
||||||
})
|
|
||||||
},
|
|
||||||
vec![(7, 1), (7, 5), (7, 20)],
|
|
||||||
)
|
|
||||||
.with_function("fastfield", |bench, (stride, num_bits)| {
|
|
||||||
let mut schema_builder = Schema::builder();
|
|
||||||
let val_field = schema_builder.add_u64_field("val", FAST);
|
|
||||||
let schema = schema_builder.build();
|
|
||||||
|
|
||||||
let index = Index::create_in_ram(schema);
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 80_000_000).unwrap();
|
|
||||||
for el in generate_permutation(*stride, *num_bits) {
|
|
||||||
index_writer.add_document(doc!(val_field=>el));
|
|
||||||
}
|
|
||||||
index_writer.commit().unwrap();
|
|
||||||
let reader = index.reader().unwrap();
|
|
||||||
let searcher = reader.searcher();
|
|
||||||
let segment_reader = searcher.segment_reader(0u32);
|
|
||||||
let fast_field_reader = segment_reader.fast_fields().u64(val_field).unwrap();
|
|
||||||
bench.iter(move || {
|
|
||||||
let mut a = 0u64;
|
|
||||||
for _ in 0..NUM_LOOKUPS {
|
|
||||||
a = fast_field_reader.get(a as DocId);
|
|
||||||
}
|
|
||||||
a
|
|
||||||
})
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_linear_lookup, bench_jumpy_lookup);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
use criterion::{criterion_group, criterion_main, Criterion};
|
|
||||||
use rand::rngs::StdRng;
|
|
||||||
use rand::{Rng, SeedableRng};
|
|
||||||
use tantivy::query::QueryParser;
|
|
||||||
use tantivy::schema::{Schema, STRING};
|
|
||||||
use tantivy::{Document, Index};
|
|
||||||
|
|
||||||
fn bench_union(criterion: &mut Criterion) {
|
|
||||||
criterion.bench_function_over_inputs(
|
|
||||||
"union_docset_fulladvance",
|
|
||||||
|bench, (ratio_left, ratio_right)| {
|
|
||||||
let mut schema_builder = Schema::builder();
|
|
||||||
let field = schema_builder.add_text_field("val", STRING);
|
|
||||||
let schema = schema_builder.build();
|
|
||||||
let index = Index::create_in_ram(schema);
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 80_000_000).unwrap();
|
|
||||||
let mut stdrng = StdRng::from_seed([0u8; 32]);
|
|
||||||
for _ in 0u32..100_000u32 {
|
|
||||||
let mut doc = Document::default();
|
|
||||||
if stdrng.gen_bool(*ratio_left) {
|
|
||||||
doc.add_text(field, "left");
|
|
||||||
}
|
|
||||||
if stdrng.gen_bool(*ratio_right) {
|
|
||||||
doc.add_text(field, "right");
|
|
||||||
}
|
|
||||||
index_writer.add_document(doc);
|
|
||||||
}
|
|
||||||
index_writer.commit().unwrap();
|
|
||||||
let reader = index.reader().unwrap();
|
|
||||||
let searcher = reader.searcher();
|
|
||||||
|
|
||||||
let query = QueryParser::for_index(&index, vec![field])
|
|
||||||
.parse_query("left right")
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
bench.iter(move || {
|
|
||||||
let weight = query.weight(&searcher, false).unwrap();
|
|
||||||
let mut scorer = weight.scorer(searcher.segment_reader(0u32)).unwrap();
|
|
||||||
let mut sum_docs = 0u64;
|
|
||||||
scorer.for_each(&mut |doc_id, _score| {
|
|
||||||
sum_docs += doc_id as u64;
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
vec![(0.2, 0.1), (0.2, 0.02)],
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_union);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -1,72 +0,0 @@
|
|||||||
use criterion::{criterion_group, criterion_main, Criterion, ParameterizedBenchmark};
|
|
||||||
use rand::rngs::StdRng;
|
|
||||||
use rand::Rng;
|
|
||||||
use rand::SeedableRng;
|
|
||||||
use tantivy::forbench::compression::{compressed_block_size, BlockDecoder};
|
|
||||||
use tantivy::forbench::compression::{BlockEncoder, VIntEncoder};
|
|
||||||
use tantivy::forbench::compression::{VIntDecoder, COMPRESSION_BLOCK_SIZE};
|
|
||||||
|
|
||||||
fn generate_array_with_seed(n: usize, ratio: f64, seed_val: u8) -> Vec<u32> {
|
|
||||||
let seed: [u8; 32] = [seed_val; 32];
|
|
||||||
let mut rng = StdRng::from_seed(seed);
|
|
||||||
(0u32..).filter(|_| rng.gen_bool(ratio)).take(n).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_array(n: usize, ratio: f64) -> Vec<u32> {
|
|
||||||
generate_array_with_seed(n, ratio, 4)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_compress(criterion: &mut Criterion) {
|
|
||||||
criterion.bench(
|
|
||||||
"compress_sorted",
|
|
||||||
ParameterizedBenchmark::new(
|
|
||||||
"bitpack",
|
|
||||||
|bench, ratio| {
|
|
||||||
let mut encoder = BlockEncoder::new();
|
|
||||||
let data = generate_array(COMPRESSION_BLOCK_SIZE, *ratio);
|
|
||||||
bench.iter(|| {
|
|
||||||
encoder.compress_block_sorted(&data, 0u32);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
vec![0.1],
|
|
||||||
)
|
|
||||||
.with_function("vint", |bench, ratio| {
|
|
||||||
let mut encoder = BlockEncoder::new();
|
|
||||||
let data = generate_array(COMPRESSION_BLOCK_SIZE, *ratio);
|
|
||||||
bench.iter(|| {
|
|
||||||
encoder.compress_vint_sorted(&data, 0u32);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bench_uncompress(criterion: &mut Criterion) {
|
|
||||||
criterion.bench(
|
|
||||||
"uncompress_sorted",
|
|
||||||
ParameterizedBenchmark::new(
|
|
||||||
"bitpack",
|
|
||||||
|bench, ratio| {
|
|
||||||
let mut encoder = BlockEncoder::new();
|
|
||||||
let data = generate_array(COMPRESSION_BLOCK_SIZE, *ratio);
|
|
||||||
let (num_bits, compressed) = encoder.compress_block_sorted(&data, 0u32);
|
|
||||||
let mut decoder = BlockDecoder::new();
|
|
||||||
bench.iter(|| {
|
|
||||||
decoder.uncompress_block_sorted(compressed, 0u32, num_bits);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
vec![0.1],
|
|
||||||
)
|
|
||||||
.with_function("vint", |bench, ratio| {
|
|
||||||
let mut encoder = BlockEncoder::new();
|
|
||||||
let data = generate_array(COMPRESSION_BLOCK_SIZE, *ratio);
|
|
||||||
let compressed = encoder.compress_vint_sorted(&data, 0u32);
|
|
||||||
let mut decoder = BlockDecoder::new();
|
|
||||||
bench.iter(move || {
|
|
||||||
decoder.uncompress_vint_sorted(compressed, 0u32, COMPRESSION_BLOCK_SIZE);
|
|
||||||
});
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
criterion_group!(benches, bench_compress, bench_uncompress);
|
|
||||||
criterion_main!(benches);
|
|
||||||
@@ -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 && bash <(curl -s https://codecov.io/bash) -s target/kcov
|
cargo build --verbose && cargo coverage --verbose --all && 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,7 +15,8 @@ main() {
|
|||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
echo "Test"
|
echo "Test"
|
||||||
cross test --target $TARGET --no-default-features --features mmap -- --test-threads 1
|
cross test --target $TARGET --no-default-features --features mmap
|
||||||
|
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
|
||||||
|
|||||||
@@ -5,20 +5,17 @@
|
|||||||
//
|
//
|
||||||
// We will :
|
// We will :
|
||||||
// - define our schema
|
// - define our schema
|
||||||
// = create an index in a directory
|
// - create an index in a directory
|
||||||
// - index few documents in our index
|
// - index a few documents into our index
|
||||||
// - search for the best document matchings "sea whale"
|
// - search for the best document matching a basic query
|
||||||
// - retrieve the best document original content.
|
// - retrieve the best document's original content.
|
||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::query::QueryParser;
|
use tantivy::query::QueryParser;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index, ReloadPolicy};
|
||||||
use tantivy::ReloadPolicy;
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
@@ -33,7 +30,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// and for each field, its type and "the way it should
|
// and for each field, its type and "the way it should
|
||||||
// be indexed".
|
// be indexed".
|
||||||
|
|
||||||
// first we need to define a schema ...
|
// First we need to define a schema ...
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
|
|
||||||
// Our first field is title.
|
// Our first field is title.
|
||||||
@@ -48,7 +45,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
//
|
//
|
||||||
// `STORED` means that the field will also be saved
|
// `STORED` means that the field will also be saved
|
||||||
// in a compressed, row-oriented key-value store.
|
// in a compressed, row-oriented key-value store.
|
||||||
// This store is useful to reconstruct the
|
// This store is useful for reconstructing the
|
||||||
// documents that were selected during the search phase.
|
// documents that were selected during the search phase.
|
||||||
schema_builder.add_text_field("title", TEXT | STORED);
|
schema_builder.add_text_field("title", TEXT | STORED);
|
||||||
|
|
||||||
@@ -57,8 +54,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// need to be able to be able to retrieve it
|
// need to be able to be able to retrieve it
|
||||||
// for our application.
|
// for our application.
|
||||||
//
|
//
|
||||||
// We can make our index lighter and
|
// We can make our index lighter by omitting the `STORED` flag.
|
||||||
// by omitting `STORED` flag.
|
|
||||||
schema_builder.add_text_field("body", TEXT);
|
schema_builder.add_text_field("body", TEXT);
|
||||||
|
|
||||||
let schema = schema_builder.build();
|
let schema = schema_builder.build();
|
||||||
@@ -71,7 +67,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// with our schema in the directory.
|
// with our schema in the directory.
|
||||||
let index = Index::create_in_dir(&index_path, schema.clone())?;
|
let index = Index::create_in_dir(&index_path, schema.clone())?;
|
||||||
|
|
||||||
// To insert document we need an index writer.
|
// To insert a document we will need an index writer.
|
||||||
// There must be only one writer at a time.
|
// There must be only one writer at a time.
|
||||||
// This single `IndexWriter` is already
|
// This single `IndexWriter` is already
|
||||||
// multithreaded.
|
// multithreaded.
|
||||||
@@ -149,8 +145,8 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// At this point our documents are not searchable.
|
// At this point our documents are not searchable.
|
||||||
//
|
//
|
||||||
//
|
//
|
||||||
// We need to call .commit() explicitly to force the
|
// We need to call `.commit()` explicitly to force the
|
||||||
// index_writer to finish processing the documents in the queue,
|
// `index_writer` to finish processing the documents in the queue,
|
||||||
// flush the current index to the disk, and advertise
|
// flush the current index to the disk, and advertise
|
||||||
// the existence of new documents.
|
// the existence of new documents.
|
||||||
//
|
//
|
||||||
@@ -162,14 +158,14 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// persistently indexed.
|
// persistently indexed.
|
||||||
//
|
//
|
||||||
// In the scenario of a crash or a power failure,
|
// In the scenario of a crash or a power failure,
|
||||||
// tantivy behaves as if has rolled back to its last
|
// tantivy behaves as if it has rolled back to its last
|
||||||
// commit.
|
// commit.
|
||||||
|
|
||||||
// # Searching
|
// # Searching
|
||||||
//
|
//
|
||||||
// ### Searcher
|
// ### Searcher
|
||||||
//
|
//
|
||||||
// A reader is required to get search the index.
|
// A reader is required first in order to search an index.
|
||||||
// It acts as a `Searcher` pool that reloads itself,
|
// It acts as a `Searcher` pool that reloads itself,
|
||||||
// depending on a `ReloadPolicy`.
|
// depending on a `ReloadPolicy`.
|
||||||
//
|
//
|
||||||
@@ -185,7 +181,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
|
|
||||||
// We now need to acquire a searcher.
|
// We now need to acquire a searcher.
|
||||||
//
|
//
|
||||||
// A searcher points to snapshotted, immutable version of the index.
|
// A searcher points to a snapshotted, immutable version of the index.
|
||||||
//
|
//
|
||||||
// Some search experience might require more than
|
// Some search experience might require more than
|
||||||
// one query. Using the same searcher ensures that all of these queries will run on the
|
// one query. Using the same searcher ensures that all of these queries will run on the
|
||||||
@@ -205,7 +201,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
// in both title and body.
|
// in both title and body.
|
||||||
let query_parser = QueryParser::for_index(&index, vec![title, body]);
|
let query_parser = QueryParser::for_index(&index, vec![title, body]);
|
||||||
|
|
||||||
// QueryParser may fail if the query is not in the right
|
// `QueryParser` may fail if the query is not in the right
|
||||||
// format. For user facing applications, this can be a problem.
|
// format. For user facing applications, this can be a problem.
|
||||||
// A ticket has been opened regarding this problem.
|
// A ticket has been opened regarding this problem.
|
||||||
let query = query_parser.parse_query("sea whale")?;
|
let query = query_parser.parse_query("sea whale")?;
|
||||||
@@ -221,7 +217,7 @@ fn main() -> tantivy::Result<()> {
|
|||||||
//
|
//
|
||||||
// We are not interested in all of the documents but
|
// We are not interested in all of the documents but
|
||||||
// only in the top 10. Keeping track of our top 10 best documents
|
// only in the top 10. Keeping track of our top 10 best documents
|
||||||
// is the role of the TopDocs.
|
// is the role of the `TopDocs` collector.
|
||||||
|
|
||||||
// We can now perform our query.
|
// We can now perform our query.
|
||||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(10))?;
|
let top_docs = searcher.search(&query, &TopDocs::with_limit(10))?;
|
||||||
|
|||||||
@@ -9,15 +9,12 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::{Collector, SegmentCollector};
|
use tantivy::collector::{Collector, SegmentCollector};
|
||||||
use tantivy::fastfield::FastFieldReader;
|
use tantivy::fastfield::FastFieldReader;
|
||||||
use tantivy::query::QueryParser;
|
use tantivy::query::QueryParser;
|
||||||
use tantivy::schema::Field;
|
use tantivy::schema::Field;
|
||||||
use tantivy::schema::{Schema, FAST, INDEXED, TEXT};
|
use tantivy::schema::{Schema, FAST, INDEXED, TEXT};
|
||||||
use tantivy::SegmentReader;
|
use tantivy::{doc, Index, SegmentReader, TantivyError};
|
||||||
use tantivy::{Index, TantivyError};
|
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Stats {
|
struct Stats {
|
||||||
|
|||||||
@@ -2,14 +2,11 @@
|
|||||||
//
|
//
|
||||||
// In this example, we'll see how to define a tokenizer pipeline
|
// In this example, we'll see how to define a tokenizer pipeline
|
||||||
// by aligning a bunch of `TokenFilter`.
|
// by aligning a bunch of `TokenFilter`.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::query::QueryParser;
|
use tantivy::query::QueryParser;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::tokenizer::NgramTokenizer;
|
use tantivy::tokenizer::NgramTokenizer;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index};
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
// # Defining the schema
|
// # Defining the schema
|
||||||
|
|||||||
@@ -8,13 +8,10 @@
|
|||||||
//
|
//
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::query::TermQuery;
|
use tantivy::query::TermQuery;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index, IndexReader};
|
||||||
use tantivy::IndexReader;
|
|
||||||
|
|
||||||
// A simple helper function to fetch a single document
|
// A simple helper function to fetch a single document
|
||||||
// given its id from our index.
|
// given its id from our index.
|
||||||
|
|||||||
@@ -12,12 +12,10 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::FacetCollector;
|
use tantivy::collector::FacetCollector;
|
||||||
use tantivy::query::AllQuery;
|
use tantivy::query::AllQuery;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
|
|||||||
@@ -2,14 +2,10 @@
|
|||||||
//
|
//
|
||||||
// Below is an example of creating an indexed integer field in your schema
|
// Below is an example of creating an indexed integer field in your schema
|
||||||
// You can use RangeQuery to get a Count of all occurrences in a given range.
|
// You can use RangeQuery to get a Count of all occurrences in a given range.
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::Count;
|
use tantivy::collector::Count;
|
||||||
use tantivy::query::RangeQuery;
|
use tantivy::query::RangeQuery;
|
||||||
use tantivy::schema::{Schema, INDEXED};
|
use tantivy::schema::{Schema, INDEXED};
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index, Result};
|
||||||
use tantivy::Result;
|
|
||||||
|
|
||||||
fn run() -> Result<()> {
|
fn run() -> Result<()> {
|
||||||
// For the sake of simplicity, this schema will only have 1 field
|
// For the sake of simplicity, this schema will only have 1 field
|
||||||
|
|||||||
@@ -9,11 +9,8 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, DocId, DocSet, Index, Postings};
|
||||||
use tantivy::{DocId, DocSet, Postings};
|
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
// We first create a schema for the sake of the
|
// We first create a schema for the sake of the
|
||||||
|
|||||||
@@ -25,14 +25,11 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use tantivy::schema::{Schema, STORED, TEXT};
|
use tantivy::schema::{Schema, STORED, TEXT};
|
||||||
use tantivy::Opstamp;
|
use tantivy::{doc, Index, IndexWriter, Opstamp};
|
||||||
use tantivy::{Index, IndexWriter};
|
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
// # Defining the schema
|
// # Defining the schema
|
||||||
@@ -49,10 +46,9 @@ 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 = {
|
let opstamp = index_writer_clone_1
|
||||||
// A read lock is sufficient here.
|
.read().unwrap() //< A read lock is sufficient here.
|
||||||
let index_writer_rlock = index_writer_clone_1.read().unwrap();
|
.add_document(
|
||||||
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 \
|
||||||
@@ -63,8 +59,7 @@ 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));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,13 +7,10 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::query::QueryParser;
|
use tantivy::query::QueryParser;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index, Snippet, SnippetGenerator};
|
||||||
use tantivy::{Snippet, SnippetGenerator};
|
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
|
|||||||
@@ -11,13 +11,11 @@
|
|||||||
|
|
||||||
// ---
|
// ---
|
||||||
// Importing tantivy...
|
// Importing tantivy...
|
||||||
#[macro_use]
|
|
||||||
extern crate tantivy;
|
|
||||||
use tantivy::collector::TopDocs;
|
use tantivy::collector::TopDocs;
|
||||||
use tantivy::query::QueryParser;
|
use tantivy::query::QueryParser;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
use tantivy::tokenizer::*;
|
use tantivy::tokenizer::*;
|
||||||
use tantivy::Index;
|
use tantivy::{doc, Index};
|
||||||
|
|
||||||
fn main() -> tantivy::Result<()> {
|
fn main() -> tantivy::Result<()> {
|
||||||
// this example assumes you understand the content in `basic_search`
|
// this example assumes you understand the content in `basic_search`
|
||||||
|
|||||||
16
query-grammar/Cargo.toml
Normal file
16
query-grammar/Cargo.toml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
[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"
|
||||||
17
query-grammar/src/lib.rs
Normal file
17
query-grammar/src/lib.rs
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
#![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,3 +1,6 @@
|
|||||||
|
use std::fmt;
|
||||||
|
use std::fmt::Write;
|
||||||
|
|
||||||
/// Defines whether a term in a query must be present,
|
/// Defines whether a term in a query must be present,
|
||||||
/// should be present or must not be present.
|
/// should be present or must not be present.
|
||||||
#[derive(Debug, Clone, Hash, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Hash, Copy, Eq, PartialEq)]
|
||||||
@@ -18,32 +21,38 @@ impl Occur {
|
|||||||
/// - `Should` => '?',
|
/// - `Should` => '?',
|
||||||
/// - `Must` => '+'
|
/// - `Must` => '+'
|
||||||
/// - `Not` => '-'
|
/// - `Not` => '-'
|
||||||
pub fn to_char(self) -> char {
|
fn to_char(self) -> char {
|
||||||
match self {
|
match self {
|
||||||
Occur::Should => '?',
|
Occur::Should => '?',
|
||||||
Occur::Must => '+',
|
Occur::Must => '+',
|
||||||
Occur::MustNot => '-',
|
Occur::MustNot => '-',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Compose two occur values.
|
/// Compose two occur values.
|
||||||
pub fn compose_occur(left: Occur, right: Occur) -> Occur {
|
pub fn compose(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 => {
|
||||||
Occur::MustNot => {
|
if right == Occur::MustNot {
|
||||||
if right == Occur::MustNot {
|
Occur::Must
|
||||||
Occur::Must
|
} else {
|
||||||
} else {
|
Occur::MustNot
|
||||||
Occur::MustNot
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Occur {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
f.write_char(self.to_char())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use super::user_input_ast::*;
|
use super::user_input_ast::*;
|
||||||
use crate::query::occur::Occur;
|
use crate::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;
|
||||||
@@ -83,28 +82,67 @@ parser! {
|
|||||||
}
|
}
|
||||||
|
|
||||||
parser! {
|
parser! {
|
||||||
|
/// Function that parses a range out of a Stream
|
||||||
|
/// Supports ranges like:
|
||||||
|
/// [5 TO 10], {5 TO 10}, [* TO 10], [10 TO *], {10 TO *], >5, <=10
|
||||||
|
/// [a TO *], [a TO c], [abc TO bcd}
|
||||||
fn range[I]()(I) -> UserInputLeaf
|
fn range[I]()(I) -> UserInputLeaf
|
||||||
where [I: Stream<Item = char>] {
|
where [I: Stream<Item = char>] {
|
||||||
let range_term_val = || {
|
let range_term_val = || {
|
||||||
word().or(negative_number()).or(char('*').with(value("*".to_string())))
|
word().or(negative_number()).or(char('*').with(value("*".to_string())))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// check for unbounded range in the form of <5, <=10, >5, >=5
|
||||||
|
let elastic_unbounded_range = (choice([attempt(string(">=")),
|
||||||
|
attempt(string("<=")),
|
||||||
|
attempt(string("<")),
|
||||||
|
attempt(string(">"))])
|
||||||
|
.skip(spaces()),
|
||||||
|
range_term_val()).
|
||||||
|
map(|(comparison_sign, bound): (&str, String)|
|
||||||
|
match comparison_sign {
|
||||||
|
">=" => (UserInputBound::Inclusive(bound), UserInputBound::Unbounded),
|
||||||
|
"<=" => (UserInputBound::Unbounded, UserInputBound::Inclusive(bound)),
|
||||||
|
"<" => (UserInputBound::Unbounded, UserInputBound::Exclusive(bound)),
|
||||||
|
">" => (UserInputBound::Exclusive(bound), UserInputBound::Unbounded),
|
||||||
|
// default case
|
||||||
|
_ => (UserInputBound::Unbounded, UserInputBound::Unbounded)
|
||||||
|
});
|
||||||
let lower_bound = (one_of("{[".chars()), range_term_val())
|
let lower_bound = (one_of("{[".chars()), range_term_val())
|
||||||
.map(|(boundary_char, lower_bound): (char, String)|
|
.map(|(boundary_char, lower_bound): (char, String)|
|
||||||
if boundary_char == '{' { UserInputBound::Exclusive(lower_bound) }
|
if lower_bound == "*" {
|
||||||
else { UserInputBound::Inclusive(lower_bound) });
|
UserInputBound::Unbounded
|
||||||
|
} else if boundary_char == '{' {
|
||||||
|
UserInputBound::Exclusive(lower_bound)
|
||||||
|
} else {
|
||||||
|
UserInputBound::Inclusive(lower_bound)
|
||||||
|
});
|
||||||
let upper_bound = (range_term_val(), one_of("}]".chars()))
|
let upper_bound = (range_term_val(), one_of("}]".chars()))
|
||||||
.map(|(higher_bound, boundary_char): (String, char)|
|
.map(|(higher_bound, boundary_char): (String, char)|
|
||||||
if boundary_char == '}' { UserInputBound::Exclusive(higher_bound) }
|
if higher_bound == "*" {
|
||||||
else { UserInputBound::Inclusive(higher_bound) });
|
UserInputBound::Unbounded
|
||||||
(
|
} else if boundary_char == '}' {
|
||||||
optional(field()),
|
UserInputBound::Exclusive(higher_bound)
|
||||||
lower_bound
|
} else {
|
||||||
.skip((spaces(), string("TO"), spaces())),
|
UserInputBound::Inclusive(higher_bound)
|
||||||
upper_bound,
|
});
|
||||||
).map(|(field, lower, upper)| UserInputLeaf::Range {
|
// return only lower and upper
|
||||||
field,
|
let lower_to_upper = (lower_bound.
|
||||||
lower,
|
skip((spaces(),
|
||||||
upper
|
string("TO"),
|
||||||
|
spaces())),
|
||||||
|
upper_bound);
|
||||||
|
|
||||||
|
(optional(field()).skip(spaces()),
|
||||||
|
// try elastic first, if it matches, the range is unbounded
|
||||||
|
attempt(elastic_unbounded_range).or(lower_to_upper))
|
||||||
|
.map(|(field, (lower, upper))|
|
||||||
|
// Construct the leaf from extracted field (optional)
|
||||||
|
// and bounds
|
||||||
|
UserInputLeaf::Range {
|
||||||
|
field,
|
||||||
|
lower,
|
||||||
|
upper
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,6 +296,49 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_elastic_query_ranges() {
|
||||||
|
test_parse_query_to_ast_helper("title: >a", "title:{\"a\" TO \"*\"}");
|
||||||
|
test_parse_query_to_ast_helper("title:>=a", "title:[\"a\" TO \"*\"}");
|
||||||
|
test_parse_query_to_ast_helper("title: <a", "title:{\"*\" TO \"a\"}");
|
||||||
|
test_parse_query_to_ast_helper("title:<=a", "title:{\"*\" TO \"a\"]");
|
||||||
|
test_parse_query_to_ast_helper("title:<=bsd", "title:{\"*\" TO \"bsd\"]");
|
||||||
|
|
||||||
|
test_parse_query_to_ast_helper("weight: >70", "weight:{\"70\" TO \"*\"}");
|
||||||
|
test_parse_query_to_ast_helper("weight:>=70", "weight:[\"70\" TO \"*\"}");
|
||||||
|
test_parse_query_to_ast_helper("weight: <70", "weight:{\"*\" TO \"70\"}");
|
||||||
|
test_parse_query_to_ast_helper("weight:<=70", "weight:{\"*\" TO \"70\"]");
|
||||||
|
test_parse_query_to_ast_helper("weight: >60.7", "weight:{\"60.7\" TO \"*\"}");
|
||||||
|
|
||||||
|
test_parse_query_to_ast_helper("weight: <= 70", "weight:{\"*\" TO \"70\"]");
|
||||||
|
|
||||||
|
test_parse_query_to_ast_helper("weight: <= 70.5", "weight:{\"*\" TO \"70.5\"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_range_parser() {
|
||||||
|
// testing the range() parser separately
|
||||||
|
let res = range().parse("title: <hello").unwrap().0;
|
||||||
|
let expected = UserInputLeaf::Range {
|
||||||
|
field: Some("title".to_string()),
|
||||||
|
lower: UserInputBound::Unbounded,
|
||||||
|
upper: UserInputBound::Exclusive("hello".to_string()),
|
||||||
|
};
|
||||||
|
let res2 = range().parse("title:{* TO hello}").unwrap().0;
|
||||||
|
assert_eq!(res, expected);
|
||||||
|
assert_eq!(res2, expected);
|
||||||
|
let expected_weight = UserInputLeaf::Range {
|
||||||
|
field: Some("weight".to_string()),
|
||||||
|
lower: UserInputBound::Inclusive("71.2".to_string()),
|
||||||
|
upper: UserInputBound::Unbounded,
|
||||||
|
};
|
||||||
|
|
||||||
|
let res3 = range().parse("weight: >=71.2").unwrap().0;
|
||||||
|
let res4 = range().parse("weight:[71.2 TO *}").unwrap().0;
|
||||||
|
assert_eq!(res3, expected_weight);
|
||||||
|
assert_eq!(res4, expected_weight);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_query_to_triming_spaces() {
|
fn test_parse_query_to_triming_spaces() {
|
||||||
test_parse_query_to_ast_helper(" abc", "\"abc\"");
|
test_parse_query_to_ast_helper(" abc", "\"abc\"");
|
||||||
@@ -291,7 +372,7 @@ mod test {
|
|||||||
test_parse_query_to_ast_helper("[1 TO 5]", "[\"1\" TO \"5\"]");
|
test_parse_query_to_ast_helper("[1 TO 5]", "[\"1\" TO \"5\"]");
|
||||||
test_parse_query_to_ast_helper("foo:{a TO z}", "foo:{\"a\" TO \"z\"}");
|
test_parse_query_to_ast_helper("foo:{a TO z}", "foo:{\"a\" TO \"z\"}");
|
||||||
test_parse_query_to_ast_helper("foo:[1 TO toto}", "foo:[\"1\" TO \"toto\"}");
|
test_parse_query_to_ast_helper("foo:[1 TO toto}", "foo:[\"1\" TO \"toto\"}");
|
||||||
test_parse_query_to_ast_helper("foo:[* TO toto}", "foo:[\"*\" TO \"toto\"}");
|
test_parse_query_to_ast_helper("foo:[* TO toto}", "foo:{\"*\" TO \"toto\"}");
|
||||||
test_parse_query_to_ast_helper("foo:[1 TO *}", "foo:[\"1\" TO \"*\"}");
|
test_parse_query_to_ast_helper("foo:[1 TO *}", "foo:[\"1\" TO \"*\"}");
|
||||||
test_parse_query_to_ast_helper("foo:[1.1 TO *}", "foo:[\"1.1\" TO \"*\"}");
|
test_parse_query_to_ast_helper("foo:[1.1 TO *}", "foo:[\"1.1\" TO \"*\"}");
|
||||||
test_is_parse_err("abc + ");
|
test_is_parse_err("abc + ");
|
||||||
@@ -1,8 +1,9 @@
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
|
|
||||||
use crate::query::Occur;
|
use crate::Occur;
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum UserInputLeaf {
|
pub enum UserInputLeaf {
|
||||||
Literal(UserInputLiteral),
|
Literal(UserInputLiteral),
|
||||||
All,
|
All,
|
||||||
@@ -35,6 +36,7 @@ impl Debug for UserInputLeaf {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub struct UserInputLiteral {
|
pub struct UserInputLiteral {
|
||||||
pub field_name: Option<String>,
|
pub field_name: Option<String>,
|
||||||
pub phrase: String,
|
pub phrase: String,
|
||||||
@@ -49,9 +51,11 @@ impl fmt::Debug for UserInputLiteral {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
pub enum UserInputBound {
|
pub enum UserInputBound {
|
||||||
Inclusive(String),
|
Inclusive(String),
|
||||||
Exclusive(String),
|
Exclusive(String),
|
||||||
|
Unbounded,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserInputBound {
|
impl UserInputBound {
|
||||||
@@ -59,6 +63,7 @@ impl UserInputBound {
|
|||||||
match *self {
|
match *self {
|
||||||
UserInputBound::Inclusive(ref word) => write!(formatter, "[\"{}\"", word),
|
UserInputBound::Inclusive(ref word) => write!(formatter, "[\"{}\"", word),
|
||||||
UserInputBound::Exclusive(ref word) => write!(formatter, "{{\"{}\"", word),
|
UserInputBound::Exclusive(ref word) => write!(formatter, "{{\"{}\"", word),
|
||||||
|
UserInputBound::Unbounded => write!(formatter, "{{\"*\""),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,6 +71,7 @@ impl UserInputBound {
|
|||||||
match *self {
|
match *self {
|
||||||
UserInputBound::Inclusive(ref word) => write!(formatter, "\"{}\"]", word),
|
UserInputBound::Inclusive(ref word) => write!(formatter, "\"{}\"]", word),
|
||||||
UserInputBound::Exclusive(ref word) => write!(formatter, "\"{}\"}}", word),
|
UserInputBound::Exclusive(ref word) => write!(formatter, "\"{}\"}}", word),
|
||||||
|
UserInputBound::Unbounded => write!(formatter, "\"*\"}}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -73,6 +79,7 @@ impl UserInputBound {
|
|||||||
match *self {
|
match *self {
|
||||||
UserInputBound::Inclusive(ref contents) => contents,
|
UserInputBound::Inclusive(ref contents) => contents,
|
||||||
UserInputBound::Exclusive(ref contents) => contents,
|
UserInputBound::Exclusive(ref contents) => contents,
|
||||||
|
UserInputBound::Unbounded => &"*",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -144,7 +151,7 @@ impl fmt::Debug for UserInputAST {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
UserInputAST::Unary(ref occur, ref subquery) => {
|
UserInputAST::Unary(ref occur, ref subquery) => {
|
||||||
write!(formatter, "{}({:?})", occur.to_char(), subquery)
|
write!(formatter, "{}({:?})", occur, subquery)
|
||||||
}
|
}
|
||||||
UserInputAST::Leaf(ref subquery) => write!(formatter, "{:?}", subquery),
|
UserInputAST::Leaf(ref subquery) => write!(formatter, "{:?}", subquery),
|
||||||
}
|
}
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
cargo bench --features forbench
|
|
||||||
@@ -10,12 +10,10 @@ use crate::SegmentReader;
|
|||||||
/// documents match the query.
|
/// documents match the query.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result};
|
|
||||||
/// use tantivy::collector::Count;
|
/// use tantivy::collector::Count;
|
||||||
/// use tantivy::query::QueryParser;
|
/// use tantivy::query::QueryParser;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, Index, Result};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
@@ -125,5 +123,4 @@ mod tests {
|
|||||||
assert_eq!(count_collector.harvest(), 2);
|
assert_eq!(count_collector.harvest(), 2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -81,12 +81,10 @@ fn facet_depth(facet_bytes: &[u8]) -> usize {
|
|||||||
///
|
///
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Facet, Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result};
|
|
||||||
/// use tantivy::collector::FacetCollector;
|
/// use tantivy::collector::FacetCollector;
|
||||||
/// use tantivy::query::AllQuery;
|
/// use tantivy::query::AllQuery;
|
||||||
|
/// use tantivy::schema::{Facet, Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, Index, Result};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
@@ -601,19 +599,18 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
mod bench {
|
mod bench {
|
||||||
|
|
||||||
use collector::FacetCollector;
|
use crate::collector::FacetCollector;
|
||||||
use query::AllQuery;
|
use crate::query::AllQuery;
|
||||||
use rand::{thread_rng, Rng};
|
use crate::schema::{Facet, Schema};
|
||||||
use schema::Facet;
|
use crate::Index;
|
||||||
use schema::Schema;
|
use rand::seq::SliceRandom;
|
||||||
|
use rand::thread_rng;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
use Index;
|
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_facet_collector(b: &mut Bencher) {
|
fn bench_facet_collector(b: &mut Bencher) {
|
||||||
@@ -630,7 +627,7 @@ mod bench {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 40425 docs
|
// 40425 docs
|
||||||
thread_rng().shuffle(&mut docs[..]);
|
docs[..].shuffle(&mut thread_rng());
|
||||||
|
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
for doc in docs {
|
for doc in docs {
|
||||||
@@ -639,7 +636,7 @@ mod bench {
|
|||||||
index_writer.commit().unwrap();
|
index_writer.commit().unwrap();
|
||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let searcher = index.searcher();
|
let searcher = reader.searcher();
|
||||||
let facet_collector = FacetCollector::for_field(facet_field);
|
let facet_collector = FacetCollector::for_field(facet_field);
|
||||||
searcher.search(&AllQuery, &facet_collector).unwrap();
|
searcher.search(&AllQuery, &facet_collector).unwrap();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ The resulting `Fruit` will then be a typed tuple with each collector's original
|
|||||||
in their respective position.
|
in their respective position.
|
||||||
|
|
||||||
```rust
|
```rust
|
||||||
# extern crate tantivy;
|
|
||||||
# use tantivy::schema::*;
|
# use tantivy::schema::*;
|
||||||
# use tantivy::*;
|
# use tantivy::*;
|
||||||
# use tantivy::query::*;
|
# use tantivy::query::*;
|
||||||
|
|||||||
@@ -105,12 +105,10 @@ impl<TFruit: Fruit> FruitHandle<TFruit> {
|
|||||||
/// [Combining several collectors section of the collector documentation](./index.html#combining-several-collectors).
|
/// [Combining several collectors section of the collector documentation](./index.html#combining-several-collectors).
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result};
|
|
||||||
/// use tantivy::collector::{Count, TopDocs, MultiCollector};
|
/// use tantivy::collector::{Count, TopDocs, MultiCollector};
|
||||||
/// use tantivy::query::QueryParser;
|
/// use tantivy::query::QueryParser;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, Index, Result};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
|
|||||||
@@ -23,13 +23,10 @@ use std::fmt;
|
|||||||
/// is `O(n log K)`.
|
/// is `O(n log K)`.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::DocAddress;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result};
|
|
||||||
/// use tantivy::collector::TopDocs;
|
/// use tantivy::collector::TopDocs;
|
||||||
/// use tantivy::query::QueryParser;
|
/// use tantivy::query::QueryParser;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, DocAddress, Index, Result};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
@@ -87,10 +84,8 @@ impl TopDocs {
|
|||||||
/// Set top-K to rank documents by a given fast field.
|
/// Set top-K to rank documents by a given fast field.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
||||||
/// # use tantivy::{Index, Result, DocAddress};
|
/// # use tantivy::{doc, Index, Result, DocAddress};
|
||||||
/// # use tantivy::query::{Query, QueryParser};
|
/// # use tantivy::query::{Query, QueryParser};
|
||||||
/// use tantivy::Searcher;
|
/// use tantivy::Searcher;
|
||||||
/// use tantivy::collector::TopDocs;
|
/// use tantivy::collector::TopDocs;
|
||||||
@@ -128,7 +123,7 @@ impl TopDocs {
|
|||||||
/// ///
|
/// ///
|
||||||
/// /// `field` is required to be a FAST field.
|
/// /// `field` is required to be a FAST field.
|
||||||
/// fn docs_sorted_by_rating(searcher: &Searcher,
|
/// fn docs_sorted_by_rating(searcher: &Searcher,
|
||||||
/// query: &Query,
|
/// query: &dyn Query,
|
||||||
/// sort_by_field: Field)
|
/// sort_by_field: Field)
|
||||||
/// -> Result<Vec<(u64, DocAddress)>> {
|
/// -> Result<Vec<(u64, DocAddress)>> {
|
||||||
///
|
///
|
||||||
@@ -197,10 +192,8 @@ impl TopDocs {
|
|||||||
/// learning-to-rank model over various features
|
/// learning-to-rank model over various features
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
||||||
/// # use tantivy::{Index, DocAddress, DocId, Score};
|
/// # use tantivy::{doc, Index, DocAddress, DocId, Score};
|
||||||
/// # use tantivy::query::QueryParser;
|
/// # use tantivy::query::QueryParser;
|
||||||
/// use tantivy::SegmentReader;
|
/// use tantivy::SegmentReader;
|
||||||
/// use tantivy::collector::TopDocs;
|
/// use tantivy::collector::TopDocs;
|
||||||
@@ -302,10 +295,8 @@ impl TopDocs {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use]
|
|
||||||
/// # extern crate tantivy;
|
|
||||||
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
/// # use tantivy::schema::{Schema, FAST, TEXT};
|
||||||
/// # use tantivy::{Index, DocAddress, DocId};
|
/// # use tantivy::{doc, Index, DocAddress, DocId};
|
||||||
/// # use tantivy::query::QueryParser;
|
/// # use tantivy::query::QueryParser;
|
||||||
/// use tantivy::SegmentReader;
|
/// use tantivy::SegmentReader;
|
||||||
/// use tantivy::collector::TopDocs;
|
/// use tantivy::collector::TopDocs;
|
||||||
@@ -601,5 +592,4 @@ mod tests {
|
|||||||
let query = query_parser.parse_query(query).unwrap();
|
let query = query_parser.parse_query(query).unwrap();
|
||||||
(index, query)
|
(index, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -86,7 +86,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub fn get(&self, idx: u64) -> u64 {
|
pub fn get(&self, idx: u64) -> u64 {
|
||||||
if self.num_bits == 0 {
|
if self.num_bits == 0 {
|
||||||
return 0u64;
|
return 0u64;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::fmt;
|
|||||||
use std::u64;
|
use std::u64;
|
||||||
|
|
||||||
#[derive(Clone, Copy, Eq, PartialEq)]
|
#[derive(Clone, Copy, Eq, PartialEq)]
|
||||||
pub struct TinySet(u64);
|
pub(crate) 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 {
|
||||||
@@ -353,3 +353,43 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::common::BinarySerializable;
|
|||||||
use crate::common::CountingWriter;
|
use crate::common::CountingWriter;
|
||||||
use crate::common::VInt;
|
use crate::common::VInt;
|
||||||
use crate::directory::ReadOnlySource;
|
use crate::directory::ReadOnlySource;
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::{TerminatingWrite, WritePtr};
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
use crate::space_usage::FieldUsage;
|
use crate::space_usage::FieldUsage;
|
||||||
use crate::space_usage::PerFieldSpaceUsage;
|
use crate::space_usage::PerFieldSpaceUsage;
|
||||||
@@ -42,7 +42,7 @@ pub struct CompositeWrite<W = WritePtr> {
|
|||||||
offsets: HashMap<FileAddr, u64>,
|
offsets: HashMap<FileAddr, u64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: Write> CompositeWrite<W> {
|
impl<W: TerminatingWrite + Write> CompositeWrite<W> {
|
||||||
/// Crate a new API writer that writes a composite file
|
/// Crate a new API writer that writes a composite file
|
||||||
/// in a given write.
|
/// in a given write.
|
||||||
pub fn wrap(w: W) -> CompositeWrite<W> {
|
pub fn wrap(w: W) -> CompositeWrite<W> {
|
||||||
@@ -91,8 +91,7 @@ impl<W: Write> CompositeWrite<W> {
|
|||||||
|
|
||||||
let footer_len = (self.write.written_bytes() - footer_offset) as u32;
|
let footer_len = (self.write.written_bytes() - footer_offset) as u32;
|
||||||
footer_len.serialize(&mut self.write)?;
|
footer_len.serialize(&mut self.write)?;
|
||||||
self.write.flush()?;
|
self.write.terminate()
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -231,5 +230,4 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use crate::directory::AntiCallToken;
|
||||||
|
use crate::directory::TerminatingWrite;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
@@ -42,6 +44,13 @@ impl<W: Write> Write for CountingWriter<W> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite> TerminatingWrite for CountingWriter<W> {
|
||||||
|
fn terminate_ref(&mut self, token: AntiCallToken) -> io::Result<()> {
|
||||||
|
self.flush()?;
|
||||||
|
self.underlying.terminate_ref(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ mod serialize;
|
|||||||
mod vint;
|
mod vint;
|
||||||
|
|
||||||
pub use self::bitset::BitSet;
|
pub use self::bitset::BitSet;
|
||||||
pub use self::bitset::TinySet;
|
pub(crate) use self::bitset::TinySet;
|
||||||
pub(crate) use self::composite_file::{CompositeFile, CompositeWrite};
|
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};
|
||||||
|
|||||||
@@ -199,10 +199,7 @@ pub mod test {
|
|||||||
fn test_serialize_string() {
|
fn test_serialize_string() {
|
||||||
assert_eq!(serialize_test(String::from("")), 1);
|
assert_eq!(serialize_test(String::from("")), 1);
|
||||||
assert_eq!(serialize_test(String::from("ぽよぽよ")), 1 + 3 * 4);
|
assert_eq!(serialize_test(String::from("ぽよぽよ")), 1 + 3 * 4);
|
||||||
assert_eq!(
|
assert_eq!(serialize_test(String::from("富士さん見える。")), 1 + 3 * 8);
|
||||||
serialize_test(String::from("富士さん見える。")),
|
|
||||||
1 + 3 * 8
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -26,9 +26,10 @@ use crate::IndexWriter;
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
use num_cpus;
|
use num_cpus;
|
||||||
use std::borrow::BorrowMut;
|
use std::borrow::BorrowMut;
|
||||||
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
fn load_metas(directory: &dyn Directory, inventory: &SegmentMetaInventory) -> Result<IndexMeta> {
|
fn load_metas(directory: &dyn Directory, inventory: &SegmentMetaInventory) -> Result<IndexMeta> {
|
||||||
@@ -173,11 +174,11 @@ impl Index {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Helper to access the tokenizer associated to a specific field.
|
/// Helper to access the tokenizer associated to a specific field.
|
||||||
pub fn tokenizer_for_field(&self, field: Field) -> Result<Box<dyn BoxedTokenizer>> {
|
pub fn tokenizer_for_field(&self, field: Field) -> Result<BoxedTokenizer> {
|
||||||
let field_entry = self.schema.get_field_entry(field);
|
let field_entry = self.schema.get_field_entry(field);
|
||||||
let field_type = field_entry.field_type();
|
let field_type = field_entry.field_type();
|
||||||
let tokenizer_manager: &TokenizerManager = self.tokenizers();
|
let tokenizer_manager: &TokenizerManager = self.tokenizers();
|
||||||
let tokenizer_name_opt: Option<Box<dyn BoxedTokenizer>> = match field_type {
|
let tokenizer_name_opt: Option<BoxedTokenizer> = match field_type {
|
||||||
FieldType::Str(text_options) => text_options
|
FieldType::Str(text_options) => text_options
|
||||||
.get_indexing_options()
|
.get_indexing_options()
|
||||||
.map(|text_indexing_options| text_indexing_options.tokenizer().to_string())
|
.map(|text_indexing_options| text_indexing_options.tokenizer().to_string())
|
||||||
@@ -216,8 +217,22 @@ impl Index {
|
|||||||
Index::open(mmap_directory)
|
Index::open(mmap_directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn inventory(&self) -> &SegmentMetaInventory {
|
/// Returns the list of the segment metas tracked by the index.
|
||||||
&self.inventory
|
///
|
||||||
|
/// Such segments can of course be part of the index,
|
||||||
|
/// but also they could be segments being currently built or in the middle of a merge
|
||||||
|
/// operation.
|
||||||
|
pub fn list_all_segment_metas(&self) -> Vec<SegmentMeta> {
|
||||||
|
self.inventory.all()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new segment_meta (Advanced user only).
|
||||||
|
///
|
||||||
|
/// As long as the `SegmentMeta` lives, the files associated with the
|
||||||
|
/// `SegmentMeta` are guaranteed to not be garbage collected, regardless of
|
||||||
|
/// whether the segment is recorded as part of the index or not.
|
||||||
|
pub fn new_segment_meta(&self, segment_id: SegmentId, max_doc: u32) -> SegmentMeta {
|
||||||
|
self.inventory.new_segment_meta(segment_id, max_doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open the index using the provided directory
|
/// Open the index using the provided directory
|
||||||
@@ -354,6 +369,11 @@ impl Index {
|
|||||||
.map(SegmentMeta::id)
|
.map(SegmentMeta::id)
|
||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the set of corrupted files
|
||||||
|
pub fn validate_checksum(&self) -> Result<HashSet<PathBuf>> {
|
||||||
|
self.directory.list_damaged().map_err(Into::into)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Index {
|
impl fmt::Debug for Index {
|
||||||
@@ -581,5 +601,4 @@ mod tests {
|
|||||||
assert_eq!(searcher.num_docs(), 8_000);
|
assert_eq!(searcher.num_docs(), 8_000);
|
||||||
assert!(mem_right_after_merge_finished < mem_right_after_commit);
|
assert!(mem_right_after_merge_finished < mem_right_after_commit);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,7 +30,6 @@ impl SegmentMetaInventory {
|
|||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[doc(hidden)]
|
|
||||||
pub fn new_segment_meta(&self, segment_id: SegmentId, max_doc: u32) -> SegmentMeta {
|
pub fn new_segment_meta(&self, segment_id: SegmentId, max_doc: u32) -> SegmentMeta {
|
||||||
let inner = InnerSegmentMeta {
|
let inner = InnerSegmentMeta {
|
||||||
segment_id,
|
segment_id,
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use std::error::Error;
|
||||||
|
use std::str::FromStr;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use std::sync::atomic;
|
use std::sync::atomic;
|
||||||
|
|
||||||
@@ -52,15 +54,51 @@ impl SegmentId {
|
|||||||
/// and the rest is random.
|
/// and the rest is random.
|
||||||
///
|
///
|
||||||
/// Picking the first 8 chars is ok to identify
|
/// Picking the first 8 chars is ok to identify
|
||||||
/// segments in a display message.
|
/// segments in a display message (e.g. a5c4dfcb).
|
||||||
pub fn short_uuid_string(&self) -> String {
|
pub fn short_uuid_string(&self) -> String {
|
||||||
(&self.0.to_simple_ref().to_string()[..8]).to_string()
|
(&self.0.to_simple_ref().to_string()[..8]).to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a segment uuid string.
|
/// Returns a segment uuid string.
|
||||||
|
///
|
||||||
|
/// It consists in 32 lowercase hexadecimal chars
|
||||||
|
/// (e.g. a5c4dfcbdfe645089129e308e26d5523)
|
||||||
pub fn uuid_string(&self) -> String {
|
pub fn uuid_string(&self) -> String {
|
||||||
self.0.to_simple_ref().to_string()
|
self.0.to_simple_ref().to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Build a `SegmentId` string from the full uuid string.
|
||||||
|
///
|
||||||
|
/// E.g. "a5c4dfcbdfe645089129e308e26d5523"
|
||||||
|
pub fn from_uuid_string(uuid_string: &str) -> Result<SegmentId, SegmentIdParseError> {
|
||||||
|
FromStr::from_str(uuid_string)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Error type used when parsing a `SegmentId` from a string fails.
|
||||||
|
pub struct SegmentIdParseError(uuid::parser::ParseError);
|
||||||
|
|
||||||
|
impl Error for SegmentIdParseError {}
|
||||||
|
|
||||||
|
impl fmt::Debug for SegmentIdParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for SegmentIdParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
self.0.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for SegmentId {
|
||||||
|
type Err = SegmentIdParseError;
|
||||||
|
|
||||||
|
fn from_str(uuid_string: &str) -> Result<Self, SegmentIdParseError> {
|
||||||
|
let uuid = Uuid::parse_str(uuid_string).map_err(SegmentIdParseError)?;
|
||||||
|
Ok(SegmentId(uuid))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for SegmentId {
|
impl fmt::Debug for SegmentId {
|
||||||
@@ -80,3 +118,18 @@ impl Ord for SegmentId {
|
|||||||
self.0.as_bytes().cmp(other.0.as_bytes())
|
self.0.as_bytes().cmp(other.0.as_bytes())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::SegmentId;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_to_uuid_string() {
|
||||||
|
let full_uuid = "a5c4dfcbdfe645089129e308e26d5523";
|
||||||
|
let segment_id = SegmentId::from_uuid_string(full_uuid).unwrap();
|
||||||
|
assert_eq!(segment_id.uuid_string(), full_uuid);
|
||||||
|
assert_eq!(segment_id.short_uuid_string(), "a5c4dfcb");
|
||||||
|
// one extra char
|
||||||
|
assert!(SegmentId::from_uuid_string("a5c4dfcbdfe645089129e308e26d5523b").is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -118,6 +118,8 @@ pub trait Directory: DirectoryClone + fmt::Debug + Send + Sync + 'static {
|
|||||||
///
|
///
|
||||||
/// Specifically, subsequent writes or flushes should
|
/// Specifically, subsequent writes or flushes should
|
||||||
/// have no effect on the returned `ReadOnlySource` object.
|
/// have no effect on the returned `ReadOnlySource` object.
|
||||||
|
///
|
||||||
|
/// You should only use this to read files create with [`open_write`]
|
||||||
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError>;
|
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError>;
|
||||||
|
|
||||||
/// Removes a file
|
/// Removes a file
|
||||||
@@ -157,6 +159,8 @@ pub trait Directory: DirectoryClone + fmt::Debug + Send + Sync + 'static {
|
|||||||
/// atomic_write.
|
/// atomic_write.
|
||||||
///
|
///
|
||||||
/// This should only be used for small files.
|
/// This should only be used for small files.
|
||||||
|
///
|
||||||
|
/// You should only use this to read files create with [`atomic_write`]
|
||||||
fn atomic_read(&self, path: &Path) -> Result<Vec<u8>, OpenReadError>;
|
fn atomic_read(&self, path: &Path) -> Result<Vec<u8>, OpenReadError>;
|
||||||
|
|
||||||
/// Atomically replace the content of a file with data.
|
/// Atomically replace the content of a file with data.
|
||||||
|
|||||||
213
src/directory/footer.rs
Normal file
213
src/directory/footer.rs
Normal file
@@ -0,0 +1,213 @@
|
|||||||
|
use crate::directory::read_only_source::ReadOnlySource;
|
||||||
|
use crate::directory::{AntiCallToken, TerminatingWrite};
|
||||||
|
use byteorder::{ByteOrder, LittleEndian};
|
||||||
|
use crc32fast::Hasher;
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
|
||||||
|
const COMMON_FOOTER_SIZE: usize = 4 * 5;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub struct Footer {
|
||||||
|
pub tantivy_version: (u32, u32, u32),
|
||||||
|
pub meta: String,
|
||||||
|
pub versioned_footer: VersionedFooter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Footer {
|
||||||
|
pub fn new(versioned_footer: VersionedFooter) -> Self {
|
||||||
|
let tantivy_version = (
|
||||||
|
env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
|
||||||
|
env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
|
||||||
|
env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
|
||||||
|
);
|
||||||
|
Footer {
|
||||||
|
tantivy_version,
|
||||||
|
meta: format!(
|
||||||
|
"tantivy {}.{}.{}, index v{}",
|
||||||
|
tantivy_version.0,
|
||||||
|
tantivy_version.1,
|
||||||
|
tantivy_version.2,
|
||||||
|
versioned_footer.version()
|
||||||
|
),
|
||||||
|
versioned_footer,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
let mut res = self.versioned_footer.to_bytes();
|
||||||
|
res.extend_from_slice(self.meta.as_bytes());
|
||||||
|
let len = res.len();
|
||||||
|
res.resize(len + COMMON_FOOTER_SIZE, 0);
|
||||||
|
let mut common_footer = &mut res[len..];
|
||||||
|
LittleEndian::write_u32(&mut common_footer, self.meta.len() as u32);
|
||||||
|
LittleEndian::write_u32(&mut common_footer[4..], self.tantivy_version.0);
|
||||||
|
LittleEndian::write_u32(&mut common_footer[8..], self.tantivy_version.1);
|
||||||
|
LittleEndian::write_u32(&mut common_footer[12..], self.tantivy_version.2);
|
||||||
|
LittleEndian::write_u32(&mut common_footer[16..], (len + COMMON_FOOTER_SIZE) as u32);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(data: &[u8]) -> Result<Self, io::Error> {
|
||||||
|
let len = data.len();
|
||||||
|
if len < COMMON_FOOTER_SIZE + 4 {
|
||||||
|
// 4 bytes for index version, stored in versioned footer
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
format!("File corrupted. The footer len must be over 24, while the entire file len is {}", len)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = LittleEndian::read_u32(&data[len - 4..]) as usize;
|
||||||
|
if len < size as usize {
|
||||||
|
return Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
format!(
|
||||||
|
"File corrupted. The footer len is {}, while the entire file len is {}",
|
||||||
|
size, len
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let footer = &data[len - size as usize..];
|
||||||
|
let meta_len = LittleEndian::read_u32(&footer[size - 20..]) as usize;
|
||||||
|
let tantivy_major = LittleEndian::read_u32(&footer[size - 16..]);
|
||||||
|
let tantivy_minor = LittleEndian::read_u32(&footer[size - 12..]);
|
||||||
|
let tantivy_patch = LittleEndian::read_u32(&footer[size - 8..]);
|
||||||
|
Ok(Footer {
|
||||||
|
tantivy_version: (tantivy_major, tantivy_minor, tantivy_patch),
|
||||||
|
meta: String::from_utf8_lossy(&footer[size - meta_len - 20..size - 20]).into_owned(),
|
||||||
|
versioned_footer: VersionedFooter::from_bytes(&footer[..size - meta_len - 20])?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn extract_footer(source: ReadOnlySource) -> Result<(Footer, ReadOnlySource), io::Error> {
|
||||||
|
let footer = Footer::from_bytes(source.as_slice())?;
|
||||||
|
let reader = source.slice_to(source.as_slice().len() - footer.size());
|
||||||
|
Ok((footer, reader))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
self.versioned_footer.size() as usize + self.meta.len() + 20
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub enum VersionedFooter {
|
||||||
|
UnknownVersion { version: u32, size: u32 },
|
||||||
|
V0(u32), // crc
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VersionedFooter {
|
||||||
|
pub fn to_bytes(&self) -> Vec<u8> {
|
||||||
|
match self {
|
||||||
|
VersionedFooter::V0(crc) => {
|
||||||
|
let mut res = vec![0; 8];
|
||||||
|
LittleEndian::write_u32(&mut res, 0);
|
||||||
|
LittleEndian::write_u32(&mut res[4..], *crc);
|
||||||
|
res
|
||||||
|
}
|
||||||
|
VersionedFooter::UnknownVersion { .. } => {
|
||||||
|
panic!("Unsupported index should never get serialized");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_bytes(footer: &[u8]) -> Result<Self, io::Error> {
|
||||||
|
assert!(footer.len() >= 4);
|
||||||
|
let version = LittleEndian::read_u32(footer);
|
||||||
|
match version {
|
||||||
|
0 => {
|
||||||
|
if footer.len() == 8 {
|
||||||
|
Ok(VersionedFooter::V0(LittleEndian::read_u32(&footer[4..])))
|
||||||
|
} else {
|
||||||
|
Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
format!(
|
||||||
|
"File corrupted. The versioned footer len is {}, while it should be 8",
|
||||||
|
footer.len()
|
||||||
|
),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
version => Ok(VersionedFooter::UnknownVersion {
|
||||||
|
version,
|
||||||
|
size: footer.len() as u32,
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn size(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
VersionedFooter::V0(_) => 8,
|
||||||
|
VersionedFooter::UnknownVersion { size, .. } => *size,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn version(&self) -> u32 {
|
||||||
|
match self {
|
||||||
|
VersionedFooter::V0(_) => 0,
|
||||||
|
VersionedFooter::UnknownVersion { version, .. } => *version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn crc(&self) -> Option<u32> {
|
||||||
|
match self {
|
||||||
|
VersionedFooter::V0(crc) => Some(*crc),
|
||||||
|
VersionedFooter::UnknownVersion { .. } => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct FooterProxy<W: TerminatingWrite> {
|
||||||
|
/// always Some except after terminate call
|
||||||
|
hasher: Option<Hasher>,
|
||||||
|
/// always Some except after terminate call
|
||||||
|
writer: Option<W>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite> FooterProxy<W> {
|
||||||
|
pub fn new(writer: W) -> Self {
|
||||||
|
FooterProxy {
|
||||||
|
hasher: Some(Hasher::new()),
|
||||||
|
writer: Some(writer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite> Write for FooterProxy<W> {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let count = self.writer.as_mut().unwrap().write(buf)?;
|
||||||
|
self.hasher.as_mut().unwrap().update(&buf[..count]);
|
||||||
|
Ok(count)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.writer.as_mut().unwrap().flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite> TerminatingWrite for FooterProxy<W> {
|
||||||
|
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
||||||
|
let crc = self.hasher.take().unwrap().finalize();
|
||||||
|
|
||||||
|
let footer = Footer::new(VersionedFooter::V0(crc)).to_bytes();
|
||||||
|
let mut writer = self.writer.take().unwrap();
|
||||||
|
writer.write_all(&footer)?;
|
||||||
|
writer.terminate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::directory::footer::{Footer, VersionedFooter};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serialize_deserialize_footer() {
|
||||||
|
let crc = 123456;
|
||||||
|
let footer = Footer::new(VersionedFooter::V0(crc));
|
||||||
|
let footer_bytes = footer.to_bytes();
|
||||||
|
|
||||||
|
assert_eq!(Footer::from_bytes(&footer_bytes).unwrap(), footer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::core::MANAGED_FILEPATH;
|
use crate::core::MANAGED_FILEPATH;
|
||||||
use crate::directory::error::{DeleteError, IOError, LockError, OpenReadError, OpenWriteError};
|
use crate::directory::error::{DeleteError, IOError, LockError, OpenReadError, OpenWriteError};
|
||||||
|
use crate::directory::footer::{Footer, FooterProxy};
|
||||||
use crate::directory::DirectoryLock;
|
use crate::directory::DirectoryLock;
|
||||||
use crate::directory::Lock;
|
use crate::directory::Lock;
|
||||||
use crate::directory::META_LOCK;
|
use crate::directory::META_LOCK;
|
||||||
@@ -8,6 +9,7 @@ use crate::directory::{WatchCallback, WatchHandle};
|
|||||||
use crate::error::DataCorruption;
|
use crate::error::DataCorruption;
|
||||||
use crate::Directory;
|
use crate::Directory;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use crc32fast::Hasher;
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::io;
|
use std::io;
|
||||||
@@ -207,17 +209,59 @@ impl ManagedDirectory {
|
|||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Verify checksum of a managed file
|
||||||
|
pub fn validate_checksum(&self, path: &Path) -> result::Result<bool, OpenReadError> {
|
||||||
|
let reader = self.directory.open_read(path)?;
|
||||||
|
let (footer, data) = Footer::extract_footer(reader)
|
||||||
|
.map_err(|err| IOError::with_path(path.to_path_buf(), err))?;
|
||||||
|
let mut hasher = Hasher::new();
|
||||||
|
hasher.update(data.as_slice());
|
||||||
|
let crc = hasher.finalize();
|
||||||
|
Ok(footer
|
||||||
|
.versioned_footer
|
||||||
|
.crc()
|
||||||
|
.map(|v| v == crc)
|
||||||
|
.unwrap_or(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// List files for which checksum does not match content
|
||||||
|
pub fn list_damaged(&self) -> result::Result<HashSet<PathBuf>, OpenReadError> {
|
||||||
|
let mut hashset = HashSet::new();
|
||||||
|
let managed_paths = self
|
||||||
|
.meta_informations
|
||||||
|
.read()
|
||||||
|
.expect("Managed directory rlock poisoned in list damaged.")
|
||||||
|
.managed_paths
|
||||||
|
.clone();
|
||||||
|
|
||||||
|
for path in managed_paths.into_iter() {
|
||||||
|
if !self.validate_checksum(&path)? {
|
||||||
|
hashset.insert(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(hashset)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Directory for ManagedDirectory {
|
impl Directory for ManagedDirectory {
|
||||||
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError> {
|
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError> {
|
||||||
self.directory.open_read(path)
|
let read_only_source = self.directory.open_read(path)?;
|
||||||
|
let (_footer, reader) = Footer::extract_footer(read_only_source)
|
||||||
|
.map_err(|err| IOError::with_path(path.to_path_buf(), err))?;
|
||||||
|
Ok(reader)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_write(&mut self, path: &Path) -> result::Result<WritePtr, OpenWriteError> {
|
fn open_write(&mut self, path: &Path) -> result::Result<WritePtr, OpenWriteError> {
|
||||||
self.register_file_as_managed(path)
|
self.register_file_as_managed(path)
|
||||||
.map_err(|e| IOError::with_path(path.to_owned(), e))?;
|
.map_err(|e| IOError::with_path(path.to_owned(), e))?;
|
||||||
self.directory.open_write(path)
|
Ok(io::BufWriter::new(Box::new(FooterProxy::new(
|
||||||
|
self.directory
|
||||||
|
.open_write(path)?
|
||||||
|
.into_inner()
|
||||||
|
.map_err(|_| ())
|
||||||
|
.expect("buffer should be empty"),
|
||||||
|
))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn atomic_write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
fn atomic_write(&mut self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||||
@@ -259,8 +303,9 @@ impl Clone for ManagedDirectory {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests_mmap_specific {
|
mod tests_mmap_specific {
|
||||||
|
|
||||||
use crate::directory::{Directory, ManagedDirectory, MmapDirectory};
|
use crate::directory::{Directory, ManagedDirectory, MmapDirectory, TerminatingWrite};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
use std::fs::OpenOptions;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
@@ -275,8 +320,8 @@ mod tests_mmap_specific {
|
|||||||
{
|
{
|
||||||
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||||
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
||||||
let mut write_file = managed_directory.open_write(test_path1).unwrap();
|
let write_file = managed_directory.open_write(test_path1).unwrap();
|
||||||
write_file.flush().unwrap();
|
write_file.terminate().unwrap();
|
||||||
managed_directory
|
managed_directory
|
||||||
.atomic_write(test_path2, &[0u8, 1u8])
|
.atomic_write(test_path2, &[0u8, 1u8])
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@@ -310,9 +355,9 @@ mod tests_mmap_specific {
|
|||||||
|
|
||||||
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||||
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
||||||
managed_directory
|
let mut write = managed_directory.open_write(test_path1).unwrap();
|
||||||
.atomic_write(test_path1, &vec![0u8, 1u8])
|
write.write_all(&[0u8, 1u8]).unwrap();
|
||||||
.unwrap();
|
write.terminate().unwrap();
|
||||||
assert!(managed_directory.exists(test_path1));
|
assert!(managed_directory.exists(test_path1));
|
||||||
|
|
||||||
let _mmap_read = managed_directory.open_read(test_path1).unwrap();
|
let _mmap_read = managed_directory.open_read(test_path1).unwrap();
|
||||||
@@ -331,4 +376,38 @@ mod tests_mmap_specific {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_checksum() {
|
||||||
|
let test_path1: &'static Path = Path::new("some_path_for_test");
|
||||||
|
let test_path2: &'static Path = Path::new("other_test_path");
|
||||||
|
|
||||||
|
let tempdir = TempDir::new().unwrap();
|
||||||
|
let tempdir_path = PathBuf::from(tempdir.path());
|
||||||
|
|
||||||
|
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||||
|
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
||||||
|
let mut write = managed_directory.open_write(test_path1).unwrap();
|
||||||
|
write.write_all(&[0u8, 1u8]).unwrap();
|
||||||
|
write.terminate().unwrap();
|
||||||
|
|
||||||
|
let mut write = managed_directory.open_write(test_path2).unwrap();
|
||||||
|
write.write_all(&[3u8, 4u8, 5u8]).unwrap();
|
||||||
|
write.terminate().unwrap();
|
||||||
|
|
||||||
|
assert!(managed_directory.list_damaged().unwrap().is_empty());
|
||||||
|
|
||||||
|
let mut corrupted_path = tempdir_path.clone();
|
||||||
|
corrupted_path.push(test_path2);
|
||||||
|
let mut file = OpenOptions::new()
|
||||||
|
.write(true)
|
||||||
|
.open(&corrupted_path)
|
||||||
|
.unwrap();
|
||||||
|
file.write_all(&[255u8]).unwrap();
|
||||||
|
file.flush().unwrap();
|
||||||
|
drop(file);
|
||||||
|
|
||||||
|
let damaged = managed_directory.list_damaged().unwrap();
|
||||||
|
assert_eq!(damaged.len(), 1);
|
||||||
|
assert!(damaged.contains(test_path2));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ use crate::directory::error::{
|
|||||||
DeleteError, IOError, OpenDirectoryError, OpenReadError, OpenWriteError,
|
DeleteError, IOError, OpenDirectoryError, OpenReadError, OpenWriteError,
|
||||||
};
|
};
|
||||||
use crate::directory::read_only_source::BoxedData;
|
use crate::directory::read_only_source::BoxedData;
|
||||||
|
use crate::directory::AntiCallToken;
|
||||||
use crate::directory::Directory;
|
use crate::directory::Directory;
|
||||||
use crate::directory::DirectoryLock;
|
use crate::directory::DirectoryLock;
|
||||||
use crate::directory::Lock;
|
use crate::directory::Lock;
|
||||||
@@ -18,7 +19,7 @@ use crate::directory::ReadOnlySource;
|
|||||||
use crate::directory::WatchCallback;
|
use crate::directory::WatchCallback;
|
||||||
use crate::directory::WatchCallbackList;
|
use crate::directory::WatchCallbackList;
|
||||||
use crate::directory::WatchHandle;
|
use crate::directory::WatchHandle;
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::{TerminatingWrite, WritePtr};
|
||||||
use atomicwrites;
|
use atomicwrites;
|
||||||
use memmap::Mmap;
|
use memmap::Mmap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
@@ -141,42 +142,28 @@ impl MmapCache {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct InnerWatcherWrapper {
|
|
||||||
_watcher: Mutex<notify::RecommendedWatcher>,
|
|
||||||
watcher_router: WatchCallbackList,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerWatcherWrapper {
|
|
||||||
pub fn new(path: &Path) -> Result<(Self, Receiver<notify::RawEvent>), notify::Error> {
|
|
||||||
let (tx, watcher_recv): (Sender<RawEvent>, Receiver<RawEvent>) = channel();
|
|
||||||
// We need to initialize the
|
|
||||||
let mut watcher = notify::raw_watcher(tx)?;
|
|
||||||
watcher.watch(path, RecursiveMode::Recursive)?;
|
|
||||||
let inner = InnerWatcherWrapper {
|
|
||||||
_watcher: Mutex::new(watcher),
|
|
||||||
watcher_router: Default::default(),
|
|
||||||
};
|
|
||||||
Ok((inner, watcher_recv))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct WatcherWrapper {
|
struct WatcherWrapper {
|
||||||
inner: Arc<InnerWatcherWrapper>,
|
_watcher: Mutex<notify::RecommendedWatcher>,
|
||||||
|
watcher_router: Arc<WatchCallbackList>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WatcherWrapper {
|
impl WatcherWrapper {
|
||||||
pub fn new(path: &Path) -> Result<Self, OpenDirectoryError> {
|
pub fn new(path: &Path) -> Result<Self, OpenDirectoryError> {
|
||||||
let (inner, watcher_recv) = InnerWatcherWrapper::new(path).map_err(|err| match err {
|
let (tx, watcher_recv): (Sender<RawEvent>, Receiver<RawEvent>) = channel();
|
||||||
notify::Error::PathNotFound => OpenDirectoryError::DoesNotExist(path.to_owned()),
|
// We need to initialize the
|
||||||
_ => {
|
let watcher = notify::raw_watcher(tx)
|
||||||
panic!("Unknown error while starting watching directory {:?}", path);
|
.and_then(|mut watcher| {
|
||||||
}
|
watcher.watch(path, RecursiveMode::Recursive)?;
|
||||||
})?;
|
Ok(watcher)
|
||||||
let watcher_wrapper = WatcherWrapper {
|
})
|
||||||
inner: Arc::new(inner),
|
.map_err(|err| match err {
|
||||||
};
|
notify::Error::PathNotFound => OpenDirectoryError::DoesNotExist(path.to_owned()),
|
||||||
let watcher_wrapper_clone = watcher_wrapper.clone();
|
_ => {
|
||||||
|
panic!("Unknown error while starting watching directory {:?}", path);
|
||||||
|
}
|
||||||
|
})?;
|
||||||
|
let watcher_router: Arc<WatchCallbackList> = Default::default();
|
||||||
|
let watcher_router_clone = watcher_router.clone();
|
||||||
thread::Builder::new()
|
thread::Builder::new()
|
||||||
.name("meta-file-watch-thread".to_string())
|
.name("meta-file-watch-thread".to_string())
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
@@ -187,7 +174,7 @@ impl WatcherWrapper {
|
|||||||
// We might want to be more accurate than this at one point.
|
// We might want to be more accurate than this at one point.
|
||||||
if let Some(filename) = changed_path.file_name() {
|
if let Some(filename) = changed_path.file_name() {
|
||||||
if filename == *META_FILEPATH {
|
if filename == *META_FILEPATH {
|
||||||
watcher_wrapper_clone.inner.watcher_router.broadcast();
|
watcher_router_clone.broadcast();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -200,13 +187,15 @@ impl WatcherWrapper {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})?;
|
||||||
.expect("Failed to spawn thread to watch meta.json");
|
Ok(WatcherWrapper {
|
||||||
Ok(watcher_wrapper)
|
_watcher: Mutex::new(watcher),
|
||||||
|
watcher_router,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn watch(&mut self, watch_callback: WatchCallback) -> WatchHandle {
|
pub fn watch(&mut self, watch_callback: WatchCallback) -> WatchHandle {
|
||||||
self.inner.watcher_router.subscribe(watch_callback)
|
self.watcher_router.subscribe(watch_callback)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +254,7 @@ impl MmapDirectoryInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(watch_wrapper) = self.watcher.write().unwrap().as_mut() {
|
if let Some(watch_wrapper) = self.watcher.write().unwrap().as_mut() {
|
||||||
return Ok(watch_wrapper.watch(watch_callback));
|
Ok(watch_wrapper.watch(watch_callback))
|
||||||
} else {
|
} else {
|
||||||
unreachable!("At this point, watch wrapper is supposed to be initialized");
|
unreachable!("At this point, watch wrapper is supposed to be initialized");
|
||||||
}
|
}
|
||||||
@@ -412,6 +401,12 @@ impl Seek for SafeFileWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TerminatingWrite for SafeFileWriter {
|
||||||
|
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
||||||
|
self.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Directory for MmapDirectory {
|
impl Directory for MmapDirectory {
|
||||||
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError> {
|
fn open_read(&self, path: &Path) -> result::Result<ReadOnlySource, OpenReadError> {
|
||||||
debug!("Open Read {:?}", path);
|
debug!("Open Read {:?}", path);
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ mod mmap_directory;
|
|||||||
|
|
||||||
mod directory;
|
mod directory;
|
||||||
mod directory_lock;
|
mod directory_lock;
|
||||||
|
mod footer;
|
||||||
mod managed_directory;
|
mod managed_directory;
|
||||||
mod ram_directory;
|
mod ram_directory;
|
||||||
mod read_only_source;
|
mod read_only_source;
|
||||||
@@ -24,18 +25,49 @@ pub use self::ram_directory::RAMDirectory;
|
|||||||
pub use self::read_only_source::ReadOnlySource;
|
pub use self::read_only_source::ReadOnlySource;
|
||||||
pub(crate) use self::watch_event_router::WatchCallbackList;
|
pub(crate) use self::watch_event_router::WatchCallbackList;
|
||||||
pub use self::watch_event_router::{WatchCallback, WatchHandle};
|
pub use self::watch_event_router::{WatchCallback, WatchHandle};
|
||||||
use std::io::{BufWriter, Write};
|
use std::io::{self, BufWriter, Write};
|
||||||
|
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
pub use self::mmap_directory::MmapDirectory;
|
pub use self::mmap_directory::MmapDirectory;
|
||||||
|
|
||||||
pub use self::managed_directory::ManagedDirectory;
|
pub use self::managed_directory::ManagedDirectory;
|
||||||
|
|
||||||
|
/// Struct used to prevent from calling [`terminate_ref`](trait.TerminatingWrite#method.terminate_ref) directly
|
||||||
|
pub struct AntiCallToken(());
|
||||||
|
|
||||||
|
/// Trait used to indicate when no more write need to be done on a writer
|
||||||
|
pub trait TerminatingWrite: Write {
|
||||||
|
/// Indicate that the writer will no longer be used. Internally call terminate_ref.
|
||||||
|
fn terminate(mut self) -> io::Result<()>
|
||||||
|
where
|
||||||
|
Self: Sized,
|
||||||
|
{
|
||||||
|
self.terminate_ref(AntiCallToken(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// You should implement this function to define custom behavior.
|
||||||
|
/// This function should flush any buffer it may hold.
|
||||||
|
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite + ?Sized> TerminatingWrite for Box<W> {
|
||||||
|
fn terminate_ref(&mut self, token: AntiCallToken) -> io::Result<()> {
|
||||||
|
self.as_mut().terminate_ref(token)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<W: TerminatingWrite> TerminatingWrite for BufWriter<W> {
|
||||||
|
fn terminate_ref(&mut self, a: AntiCallToken) -> io::Result<()> {
|
||||||
|
self.flush()?;
|
||||||
|
self.get_mut().terminate_ref(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Write object for Directory.
|
/// Write object for Directory.
|
||||||
///
|
///
|
||||||
/// `WritePtr` are required to implement both Write
|
/// `WritePtr` are required to implement both Write
|
||||||
/// and Seek.
|
/// and Seek.
|
||||||
pub type WritePtr = BufWriter<Box<dyn Write>>;
|
pub type WritePtr = BufWriter<Box<dyn TerminatingWrite>>;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
use crate::core::META_FILEPATH;
|
use crate::core::META_FILEPATH;
|
||||||
use crate::directory::error::{DeleteError, OpenReadError, OpenWriteError};
|
use crate::directory::error::{DeleteError, OpenReadError, OpenWriteError};
|
||||||
|
use crate::directory::AntiCallToken;
|
||||||
use crate::directory::WatchCallbackList;
|
use crate::directory::WatchCallbackList;
|
||||||
use crate::directory::WritePtr;
|
|
||||||
use crate::directory::{Directory, ReadOnlySource, WatchCallback, WatchHandle};
|
use crate::directory::{Directory, ReadOnlySource, WatchCallback, WatchHandle};
|
||||||
|
use crate::directory::{TerminatingWrite, WritePtr};
|
||||||
use fail::fail_point;
|
use fail::fail_point;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -71,6 +72,12 @@ impl Write for VecWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl TerminatingWrite for VecWriter {
|
||||||
|
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
||||||
|
self.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct InnerDirectory {
|
struct InnerDirectory {
|
||||||
fs: HashMap<PathBuf, ReadOnlySource>,
|
fs: HashMap<PathBuf, ReadOnlySource>,
|
||||||
|
|||||||
@@ -127,7 +127,7 @@ fn test_watch(directory: &mut dyn Directory) {
|
|||||||
assert!(directory
|
assert!(directory
|
||||||
.atomic_write(Path::new("meta.json"), b"random_test_data_2")
|
.atomic_write(Path::new("meta.json"), b"random_test_data_2")
|
||||||
.is_ok());
|
.is_ok());
|
||||||
for _ in 0..100 {
|
for _ in 0..1_000 {
|
||||||
if counter.load(Ordering::SeqCst) > i {
|
if counter.load(Ordering::SeqCst) > i {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -152,5 +152,4 @@ mod tests {
|
|||||||
thread::sleep(Duration::from_millis(WAIT_TIME));
|
thread::sleep(Duration::from_millis(WAIT_TIME));
|
||||||
assert_eq!(2, counter.load(Ordering::SeqCst));
|
assert_eq!(2, counter.load(Ordering::SeqCst));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -429,5 +429,111 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
|
mod bench {
|
||||||
|
use super::tests::FIELD;
|
||||||
|
use super::tests::{generate_permutation, SCHEMA};
|
||||||
|
use super::*;
|
||||||
|
use crate::common::CompositeFile;
|
||||||
|
use crate::directory::{Directory, RAMDirectory, WritePtr};
|
||||||
|
use crate::fastfield::FastFieldReader;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::path::Path;
|
||||||
|
use test::{self, Bencher};
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_intfastfield_linear_veclookup(b: &mut Bencher) {
|
||||||
|
let permutation = generate_permutation();
|
||||||
|
b.iter(|| {
|
||||||
|
let n = test::black_box(7000u32);
|
||||||
|
let mut a = 0u64;
|
||||||
|
for i in (0u32..n / 7).map(|v| v * 7) {
|
||||||
|
a ^= permutation[i as usize];
|
||||||
|
}
|
||||||
|
a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_intfastfield_veclookup(b: &mut Bencher) {
|
||||||
|
let permutation = generate_permutation();
|
||||||
|
b.iter(|| {
|
||||||
|
let n = test::black_box(1000u32);
|
||||||
|
let mut a = 0u64;
|
||||||
|
for _ in 0u32..n {
|
||||||
|
a = permutation[a as usize];
|
||||||
|
}
|
||||||
|
a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_intfastfield_linear_fflookup(b: &mut Bencher) {
|
||||||
|
let path = Path::new("test");
|
||||||
|
let permutation = generate_permutation();
|
||||||
|
let mut directory: RAMDirectory = RAMDirectory::create();
|
||||||
|
{
|
||||||
|
let write: WritePtr = directory.open_write(Path::new("test")).unwrap();
|
||||||
|
let mut serializer = FastFieldSerializer::from_write(write).unwrap();
|
||||||
|
let mut fast_field_writers = FastFieldsWriter::from_schema(&SCHEMA);
|
||||||
|
for &x in &permutation {
|
||||||
|
fast_field_writers.add_document(&doc!(*FIELD=>x));
|
||||||
|
}
|
||||||
|
fast_field_writers
|
||||||
|
.serialize(&mut serializer, &HashMap::new())
|
||||||
|
.unwrap();
|
||||||
|
serializer.close().unwrap();
|
||||||
|
}
|
||||||
|
let source = directory.open_read(&path).unwrap();
|
||||||
|
{
|
||||||
|
let fast_fields_composite = CompositeFile::open(&source).unwrap();
|
||||||
|
let data = fast_fields_composite.open_read(*FIELD).unwrap();
|
||||||
|
let fast_field_reader = FastFieldReader::<u64>::open(data);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let n = test::black_box(7000u32);
|
||||||
|
let mut a = 0u64;
|
||||||
|
for i in (0u32..n / 7).map(|val| val * 7) {
|
||||||
|
a ^= fast_field_reader.get(i);
|
||||||
|
}
|
||||||
|
a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_intfastfield_fflookup(b: &mut Bencher) {
|
||||||
|
let path = Path::new("test");
|
||||||
|
let permutation = generate_permutation();
|
||||||
|
let mut directory: RAMDirectory = RAMDirectory::create();
|
||||||
|
{
|
||||||
|
let write: WritePtr = directory.open_write(Path::new("test")).unwrap();
|
||||||
|
let mut serializer = FastFieldSerializer::from_write(write).unwrap();
|
||||||
|
let mut fast_field_writers = FastFieldsWriter::from_schema(&SCHEMA);
|
||||||
|
for &x in &permutation {
|
||||||
|
fast_field_writers.add_document(&doc!(*FIELD=>x));
|
||||||
|
}
|
||||||
|
fast_field_writers
|
||||||
|
.serialize(&mut serializer, &HashMap::new())
|
||||||
|
.unwrap();
|
||||||
|
serializer.close().unwrap();
|
||||||
|
}
|
||||||
|
let source = directory.open_read(&path).unwrap();
|
||||||
|
{
|
||||||
|
let fast_fields_composite = CompositeFile::open(&source).unwrap();
|
||||||
|
let data = fast_fields_composite.open_read(*FIELD).unwrap();
|
||||||
|
let fast_field_reader = FastFieldReader::<u64>::open(data);
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let n = test::black_box(1000u32);
|
||||||
|
let mut a = 0u32;
|
||||||
|
for _ in 0u32..n {
|
||||||
|
a = fast_field_reader.get(a) as u32;
|
||||||
|
}
|
||||||
|
a
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ use crate::postings::UnorderedTermId;
|
|||||||
use crate::schema::{Document, Field};
|
use crate::schema::{Document, Field};
|
||||||
use crate::termdict::TermOrdinal;
|
use crate::termdict::TermOrdinal;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
|
use fnv::FnvHashMap;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Writer for multi-valued (as in, more than one value per document)
|
/// Writer for multi-valued (as in, more than one value per document)
|
||||||
@@ -102,7 +102,7 @@ impl MultiValueIntFastFieldWriter {
|
|||||||
pub fn serialize(
|
pub fn serialize(
|
||||||
&self,
|
&self,
|
||||||
serializer: &mut FastFieldSerializer,
|
serializer: &mut FastFieldSerializer,
|
||||||
mapping_opt: Option<&HashMap<UnorderedTermId, TermOrdinal>>,
|
mapping_opt: Option<&FnvHashMap<UnorderedTermId, TermOrdinal>>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
{
|
{
|
||||||
// writing the offset index
|
// writing the offset index
|
||||||
|
|||||||
@@ -67,12 +67,10 @@ impl<Item: FastValue> FastFieldReader<Item> {
|
|||||||
///
|
///
|
||||||
/// May panic if `doc` is greater than the segment
|
/// May panic if `doc` is greater than the segment
|
||||||
// `maxdoc`.
|
// `maxdoc`.
|
||||||
#[inline(always)]
|
|
||||||
pub fn get(&self, doc: DocId) -> Item {
|
pub fn get(&self, doc: DocId) -> Item {
|
||||||
self.get_u64(u64::from(doc))
|
self.get_u64(u64::from(doc))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
|
||||||
pub(crate) fn get_u64(&self, doc: u64) -> Item {
|
pub(crate) fn get_u64(&self, doc: u64) -> Item {
|
||||||
Item::from_u64(self.min_value_u64 + self.bit_unpacker.get(doc))
|
Item::from_u64(self.min_value_u64 + self.bit_unpacker.get(doc))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ use crate::fastfield::{BytesFastFieldWriter, FastFieldSerializer};
|
|||||||
use crate::postings::UnorderedTermId;
|
use crate::postings::UnorderedTermId;
|
||||||
use crate::schema::{Cardinality, Document, Field, FieldType, Schema};
|
use crate::schema::{Cardinality, Document, Field, FieldType, Schema};
|
||||||
use crate::termdict::TermOrdinal;
|
use crate::termdict::TermOrdinal;
|
||||||
|
use fnv::FnvHashMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
@@ -116,7 +117,7 @@ impl FastFieldsWriter {
|
|||||||
pub fn serialize(
|
pub fn serialize(
|
||||||
&self,
|
&self,
|
||||||
serializer: &mut FastFieldSerializer,
|
serializer: &mut FastFieldSerializer,
|
||||||
mapping: &HashMap<Field, HashMap<UnorderedTermId, TermOrdinal>>,
|
mapping: &HashMap<Field, FnvHashMap<UnorderedTermId, TermOrdinal>>,
|
||||||
) -> io::Result<()> {
|
) -> io::Result<()> {
|
||||||
for field_writer in &self.single_value_writers {
|
for field_writer in &self.single_value_writers {
|
||||||
field_writer.serialize(serializer)?;
|
field_writer.serialize(serializer)?;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use crate::core::SegmentId;
|
|||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::core::SegmentReader;
|
use crate::core::SegmentReader;
|
||||||
use crate::directory::DirectoryLock;
|
use crate::directory::DirectoryLock;
|
||||||
|
use crate::directory::TerminatingWrite;
|
||||||
use crate::docset::DocSet;
|
use crate::docset::DocSet;
|
||||||
use crate::error::TantivyError;
|
use crate::error::TantivyError;
|
||||||
use crate::fastfield::write_delete_bitset;
|
use crate::fastfield::write_delete_bitset;
|
||||||
@@ -168,6 +169,7 @@ pub(crate) fn advance_deletes(
|
|||||||
segment = segment.with_delete_meta(num_deleted_docs as u32, target_opstamp);
|
segment = segment.with_delete_meta(num_deleted_docs as u32, target_opstamp);
|
||||||
let mut delete_file = segment.open_write(SegmentComponent::DELETE)?;
|
let mut delete_file = segment.open_write(SegmentComponent::DELETE)?;
|
||||||
write_delete_bitset(&delete_bitset, &mut delete_file)?;
|
write_delete_bitset(&delete_bitset, &mut delete_file)?;
|
||||||
|
delete_file.terminate()?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
segment_entry.set_meta(segment.meta().clone());
|
segment_entry.set_meta(segment.meta().clone());
|
||||||
@@ -209,10 +211,7 @@ fn index_documents(
|
|||||||
assert!(num_docs > 0);
|
assert!(num_docs > 0);
|
||||||
|
|
||||||
let doc_opstamps: Vec<Opstamp> = segment_writer.finalize()?;
|
let doc_opstamps: Vec<Opstamp> = segment_writer.finalize()?;
|
||||||
let segment_meta = segment
|
let segment_meta = segment.index().new_segment_meta(segment_id, num_docs);
|
||||||
.index()
|
|
||||||
.inventory()
|
|
||||||
.new_segment_meta(segment_id, num_docs);
|
|
||||||
|
|
||||||
let last_docstamp: Opstamp = *(doc_opstamps.last().unwrap());
|
let last_docstamp: Opstamp = *(doc_opstamps.last().unwrap());
|
||||||
|
|
||||||
@@ -450,12 +449,10 @@ impl IndexWriter {
|
|||||||
/// by clearing and resubmitting necessary documents
|
/// by clearing and resubmitting necessary documents
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::query::QueryParser;
|
|
||||||
/// use tantivy::collector::TopDocs;
|
/// use tantivy::collector::TopDocs;
|
||||||
|
/// use tantivy::query::QueryParser;
|
||||||
/// use tantivy::schema::*;
|
/// use tantivy::schema::*;
|
||||||
/// use tantivy::Index;
|
/// use tantivy::{doc, Index};
|
||||||
///
|
///
|
||||||
/// fn main() -> tantivy::Result<()> {
|
/// fn main() -> tantivy::Result<()> {
|
||||||
/// let mut schema_builder = Schema::builder();
|
/// let mut schema_builder = Schema::builder();
|
||||||
@@ -1182,5 +1179,4 @@ mod tests {
|
|||||||
assert!(clear_again.is_ok());
|
assert!(clear_again.is_ok());
|
||||||
assert!(commit_again.is_ok());
|
assert!(commit_again.is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -134,5 +134,4 @@ mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(segment_ids(&segment_register), vec![segment_id_merged]);
|
assert_eq!(segment_ids(&segment_register), vec![segment_id_merged]);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,9 +126,7 @@ fn perform_merge(
|
|||||||
|
|
||||||
let num_docs = merger.write(segment_serializer)?;
|
let num_docs = merger.write(segment_serializer)?;
|
||||||
|
|
||||||
let segment_meta = index
|
let segment_meta = index.new_segment_meta(merged_segment.id(), num_docs);
|
||||||
.inventory()
|
|
||||||
.new_segment_meta(merged_segment.id(), num_docs);
|
|
||||||
|
|
||||||
let after_merge_segment_entry = SegmentEntry::new(segment_meta.clone(), delete_cursor, None);
|
let after_merge_segment_entry = SegmentEntry::new(segment_meta.clone(), delete_cursor, None);
|
||||||
Ok(after_merge_segment_entry)
|
Ok(after_merge_segment_entry)
|
||||||
@@ -282,7 +280,7 @@ impl SegmentUpdater {
|
|||||||
fn list_files(&self) -> HashSet<PathBuf> {
|
fn list_files(&self) -> HashSet<PathBuf> {
|
||||||
let mut files = HashSet::new();
|
let mut files = HashSet::new();
|
||||||
files.insert(META_FILEPATH.to_path_buf());
|
files.insert(META_FILEPATH.to_path_buf());
|
||||||
for segment_meta in self.0.index.inventory().all() {
|
for segment_meta in self.0.index.list_all_segment_metas() {
|
||||||
files.extend(segment_meta.list_files());
|
files.extend(segment_meta.list_files());
|
||||||
}
|
}
|
||||||
files
|
files
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ pub struct SegmentWriter {
|
|||||||
fast_field_writers: FastFieldsWriter,
|
fast_field_writers: FastFieldsWriter,
|
||||||
fieldnorms_writer: FieldNormsWriter,
|
fieldnorms_writer: FieldNormsWriter,
|
||||||
doc_opstamps: Vec<Opstamp>,
|
doc_opstamps: Vec<Opstamp>,
|
||||||
tokenizers: Vec<Option<Box<dyn BoxedTokenizer>>>,
|
tokenizers: Vec<Option<BoxedTokenizer>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentWriter {
|
impl SegmentWriter {
|
||||||
@@ -296,5 +296,4 @@ mod tests {
|
|||||||
assert_eq!(initial_table_size(10_000_000).unwrap(), 17);
|
assert_eq!(initial_table_size(10_000_000).unwrap(), 17);
|
||||||
assert_eq!(initial_table_size(1_000_000_000).unwrap(), 19);
|
assert_eq!(initial_table_size(1_000_000_000).unwrap(), 19);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/lib.rs
27
src/lib.rs
@@ -1,5 +1,4 @@
|
|||||||
#![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))))]
|
||||||
@@ -11,21 +10,12 @@
|
|||||||
//! Think `Lucene`, but in Rust.
|
//! Think `Lucene`, but in Rust.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
|
|
||||||
//! # extern crate tempfile;
|
|
||||||
//! #
|
|
||||||
//! #[macro_use]
|
|
||||||
//! extern crate tantivy;
|
|
||||||
//!
|
|
||||||
//! // ...
|
|
||||||
//!
|
|
||||||
//! # use std::path::Path;
|
//! # use std::path::Path;
|
||||||
//! # use tempfile::TempDir;
|
//! # use tempfile::TempDir;
|
||||||
//! # use tantivy::Index;
|
|
||||||
//! # use tantivy::schema::*;
|
|
||||||
//! # use tantivy::{Score, DocAddress};
|
|
||||||
//! # use tantivy::collector::TopDocs;
|
//! # use tantivy::collector::TopDocs;
|
||||||
//! # use tantivy::query::QueryParser;
|
//! # use tantivy::query::QueryParser;
|
||||||
|
//! # use tantivy::schema::*;
|
||||||
|
//! # use tantivy::{doc, DocAddress, Index, Score};
|
||||||
//! #
|
//! #
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
//! # // Let's create a temporary directory for the
|
//! # // Let's create a temporary directory for the
|
||||||
@@ -249,6 +239,7 @@ pub struct DocAddress(pub SegmentLocalId, pub DocId);
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use crate::collector::tests::TEST_COLLECTOR_WITH_SCORE;
|
use crate::collector::tests::TEST_COLLECTOR_WITH_SCORE;
|
||||||
use crate::core::SegmentReader;
|
use crate::core::SegmentReader;
|
||||||
use crate::docset::DocSet;
|
use crate::docset::DocSet;
|
||||||
@@ -894,15 +885,3 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "forbench")]
|
|
||||||
pub mod forbench {
|
|
||||||
pub mod compression {
|
|
||||||
pub use crate::postings::compression::*;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod bitset {
|
|
||||||
pub use crate::common::BitSet;
|
|
||||||
pub use crate::common::TinySet;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -22,11 +22,9 @@
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
///
|
|
||||||
/// use tantivy::schema::{Schema, TEXT, FAST};
|
/// use tantivy::schema::{Schema, TEXT, FAST};
|
||||||
|
/// use tantivy::doc;
|
||||||
///
|
///
|
||||||
/// //...
|
/// //...
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -160,9 +160,9 @@ impl VIntEncoder for BlockEncoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl VIntDecoder for BlockDecoder {
|
impl VIntDecoder for BlockDecoder {
|
||||||
fn uncompress_vint_sorted(
|
fn uncompress_vint_sorted<'a>(
|
||||||
&mut self,
|
&mut self,
|
||||||
compressed_data: &[u8],
|
compressed_data: &'a [u8],
|
||||||
offset: u32,
|
offset: u32,
|
||||||
num_els: usize,
|
num_els: usize,
|
||||||
) -> usize {
|
) -> usize {
|
||||||
@@ -170,7 +170,7 @@ impl VIntDecoder for BlockDecoder {
|
|||||||
vint::uncompress_sorted(compressed_data, &mut self.output.0[..num_els], offset)
|
vint::uncompress_sorted(compressed_data, &mut self.output.0[..num_els], offset)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn uncompress_vint_unsorted(&mut self, compressed_data: &[u8], num_els: usize) -> usize {
|
fn uncompress_vint_unsorted<'a>(&mut self, compressed_data: &'a [u8], num_els: usize) -> usize {
|
||||||
self.output_len = num_els;
|
self.output_len = num_els;
|
||||||
vint::uncompress_unsorted(compressed_data, &mut self.output.0[..num_els])
|
vint::uncompress_unsorted(compressed_data, &mut self.output.0[..num_els])
|
||||||
}
|
}
|
||||||
@@ -268,17 +268,80 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
|
mod bench {
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
use rand::rngs::StdRng;
|
||||||
|
use rand::Rng;
|
||||||
|
use rand::SeedableRng;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
fn generate_array_with_seed(n: usize, ratio: f64, seed_val: u8) -> Vec<u32> {
|
||||||
|
let mut seed: [u8; 32] = [0; 32];
|
||||||
|
seed[31] = seed_val;
|
||||||
|
let mut rng = StdRng::from_seed(seed);
|
||||||
|
(0u32..).filter(|_| rng.gen_bool(ratio)).take(n).collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_array(n: usize, ratio: f64) -> Vec<u32> {
|
||||||
|
generate_array_with_seed(n, ratio, 4)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_compress(b: &mut Bencher) {
|
||||||
|
let mut encoder = BlockEncoder::new();
|
||||||
|
let data = generate_array(COMPRESSION_BLOCK_SIZE, 0.1);
|
||||||
|
b.iter(|| {
|
||||||
|
encoder.compress_block_sorted(&data, 0u32);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_uncompress(b: &mut Bencher) {
|
||||||
|
let mut encoder = BlockEncoder::new();
|
||||||
|
let data = generate_array(COMPRESSION_BLOCK_SIZE, 0.1);
|
||||||
|
let (num_bits, compressed) = encoder.compress_block_sorted(&data, 0u32);
|
||||||
|
let mut decoder = BlockDecoder::new();
|
||||||
|
b.iter(|| {
|
||||||
|
decoder.uncompress_block_sorted(compressed, 0u32, num_bits);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_all_docs_compression_numbits() {
|
fn test_all_docs_compression_numbits() {
|
||||||
for expected_num_bits in 0u8..33u8 {
|
for expected_num_bits in 0u8.. {
|
||||||
let mut data = [0u32; 128];
|
let mut data = [0u32; 128];
|
||||||
if expected_num_bits > 0 {
|
if expected_num_bits > 0 {
|
||||||
data[0] = (1u64 << (expected_num_bits as u64) - 1u64) as u32;
|
data[0] = (1u64 << (expected_num_bits as usize) - 1) as u32;
|
||||||
}
|
}
|
||||||
let mut encoder = BlockEncoder::new();
|
let mut encoder = BlockEncoder::new();
|
||||||
let (num_bits, compressed) = encoder.compress_block_unsorted(&data);
|
let (num_bits, compressed) = encoder.compress_block_unsorted(&data);
|
||||||
assert_eq!(compressed.len(), compressed_block_size(num_bits));
|
assert_eq!(compressed.len(), compressed_block_size(num_bits));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const NUM_INTS_BENCH_VINT: usize = 10;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_compress_vint(b: &mut Bencher) {
|
||||||
|
let mut encoder = BlockEncoder::new();
|
||||||
|
let data = generate_array(NUM_INTS_BENCH_VINT, 0.001);
|
||||||
|
b.iter(|| {
|
||||||
|
encoder.compress_vint_sorted(&data, 0u32);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_uncompress_vint(b: &mut Bencher) {
|
||||||
|
let mut encoder = BlockEncoder::new();
|
||||||
|
let data = generate_array(NUM_INTS_BENCH_VINT, 0.001);
|
||||||
|
let compressed = encoder.compress_vint_sorted(&data, 0u32);
|
||||||
|
let mut decoder = BlockDecoder::new();
|
||||||
|
b.iter(|| {
|
||||||
|
decoder.uncompress_vint_sorted(compressed, 0u32, NUM_INTS_BENCH_VINT);
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ Postings module (also called inverted index)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mod block_search;
|
mod block_search;
|
||||||
|
|
||||||
pub(crate) mod compression;
|
pub(crate) mod compression;
|
||||||
/// Postings module
|
/// Postings module
|
||||||
///
|
///
|
||||||
@@ -623,23 +622,23 @@ pub mod tests {
|
|||||||
assert!(!postings_unopt.advance());
|
assert!(!postings_unopt.advance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
mod bench {
|
mod bench {
|
||||||
|
|
||||||
use super::tests::*;
|
use super::tests::*;
|
||||||
use docset::SkipResult;
|
use crate::docset::SkipResult;
|
||||||
use query::Intersection;
|
use crate::query::Intersection;
|
||||||
use schema::IndexRecordOption;
|
use crate::schema::IndexRecordOption;
|
||||||
|
use crate::tests;
|
||||||
|
use crate::DocSet;
|
||||||
use test::{self, Bencher};
|
use test::{self, Bencher};
|
||||||
use tests;
|
|
||||||
use DocSet;
|
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_segment_postings(b: &mut Bencher) {
|
fn bench_segment_postings(b: &mut Bencher) {
|
||||||
let searcher = INDEX.searcher();
|
let reader = INDEX.reader().unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
let segment_reader = searcher.segment_reader(0);
|
let segment_reader = searcher.segment_reader(0);
|
||||||
|
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
@@ -653,7 +652,8 @@ mod bench {
|
|||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_segment_intersection(b: &mut Bencher) {
|
fn bench_segment_intersection(b: &mut Bencher) {
|
||||||
let searcher = INDEX.searcher();
|
let reader = INDEX.reader().unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
let segment_reader = searcher.segment_reader(0);
|
let segment_reader = searcher.segment_reader(0);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let segment_postings_a = segment_reader
|
let segment_postings_a = segment_reader
|
||||||
@@ -683,7 +683,8 @@ mod bench {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn bench_skip_next(p: f64, b: &mut Bencher) {
|
fn bench_skip_next(p: f64, b: &mut Bencher) {
|
||||||
let searcher = INDEX.searcher();
|
let reader = INDEX.reader().unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
let segment_reader = searcher.segment_reader(0);
|
let segment_reader = searcher.segment_reader(0);
|
||||||
let docs = tests::sample(segment_reader.num_docs(), p);
|
let docs = tests::sample(segment_reader.num_docs(), p);
|
||||||
|
|
||||||
@@ -738,7 +739,8 @@ mod bench {
|
|||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn bench_iterate_segment_postings(b: &mut Bencher) {
|
fn bench_iterate_segment_postings(b: &mut Bencher) {
|
||||||
let searcher = INDEX.searcher();
|
let reader = INDEX.reader().unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
let segment_reader = searcher.segment_reader(0);
|
let segment_reader = searcher.segment_reader(0);
|
||||||
b.iter(|| {
|
b.iter(|| {
|
||||||
let n: u32 = test::black_box(17);
|
let n: u32 = test::black_box(17);
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ use crate::tokenizer::TokenStream;
|
|||||||
use crate::tokenizer::{Token, MAX_TOKEN_LEN};
|
use crate::tokenizer::{Token, MAX_TOKEN_LEN};
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use crate::Result;
|
use crate::Result;
|
||||||
|
use fnv::FnvHashMap;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
@@ -127,12 +128,12 @@ impl MultiFieldPostingsWriter {
|
|||||||
pub fn serialize(
|
pub fn serialize(
|
||||||
&self,
|
&self,
|
||||||
serializer: &mut InvertedIndexSerializer,
|
serializer: &mut InvertedIndexSerializer,
|
||||||
) -> Result<HashMap<Field, HashMap<UnorderedTermId, TermOrdinal>>> {
|
) -> Result<HashMap<Field, FnvHashMap<UnorderedTermId, TermOrdinal>>> {
|
||||||
let mut term_offsets: Vec<(&[u8], Addr, UnorderedTermId)> =
|
let mut term_offsets: Vec<(&[u8], Addr, UnorderedTermId)> =
|
||||||
self.term_index.iter().collect();
|
self.term_index.iter().collect();
|
||||||
term_offsets.sort_unstable_by_key(|&(k, _, _)| k);
|
term_offsets.sort_unstable_by_key(|&(k, _, _)| k);
|
||||||
|
|
||||||
let mut unordered_term_mappings: HashMap<Field, HashMap<UnorderedTermId, TermOrdinal>> =
|
let mut unordered_term_mappings: HashMap<Field, FnvHashMap<UnorderedTermId, TermOrdinal>> =
|
||||||
HashMap::new();
|
HashMap::new();
|
||||||
|
|
||||||
let field_offsets = make_field_partition(&term_offsets);
|
let field_offsets = make_field_partition(&term_offsets);
|
||||||
@@ -147,7 +148,7 @@ impl MultiFieldPostingsWriter {
|
|||||||
let unordered_term_ids = term_offsets[start..stop]
|
let unordered_term_ids = term_offsets[start..stop]
|
||||||
.iter()
|
.iter()
|
||||||
.map(|&(_, _, bucket)| bucket);
|
.map(|&(_, _, bucket)| bucket);
|
||||||
let mapping: HashMap<UnorderedTermId, TermOrdinal> = unordered_term_ids
|
let mapping: FnvHashMap<UnorderedTermId, TermOrdinal> = unordered_term_ids
|
||||||
.enumerate()
|
.enumerate()
|
||||||
.map(|(term_ord, unord_term_id)| {
|
.map(|(term_ord, unord_term_id)| {
|
||||||
(unord_term_id as UnorderedTermId, term_ord as TermOrdinal)
|
(unord_term_id as UnorderedTermId, term_ord as TermOrdinal)
|
||||||
|
|||||||
@@ -141,10 +141,7 @@ impl<'a> FieldSerializer<'a> {
|
|||||||
FieldType::Str(ref text_options) => {
|
FieldType::Str(ref text_options) => {
|
||||||
if let Some(text_indexing_options) = text_options.get_indexing_options() {
|
if let Some(text_indexing_options) = text_options.get_indexing_options() {
|
||||||
let index_option = text_indexing_options.index_option();
|
let index_option = text_indexing_options.index_option();
|
||||||
(
|
(index_option.has_freq(), index_option.has_positions())
|
||||||
index_option.is_termfreq_enabled(),
|
|
||||||
index_option.is_position_enabled(),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
(false, false)
|
(false, false)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -310,6 +310,7 @@ mod bench {
|
|||||||
use super::super::MemoryArena;
|
use super::super::MemoryArena;
|
||||||
use super::ExpUnrolledLinkedList;
|
use super::ExpUnrolledLinkedList;
|
||||||
use byteorder::{NativeEndian, WriteBytesExt};
|
use byteorder::{NativeEndian, WriteBytesExt};
|
||||||
|
use std::iter;
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
const NUM_STACK: usize = 10_000;
|
const NUM_STACK: usize = 10_000;
|
||||||
@@ -335,11 +336,10 @@ mod bench {
|
|||||||
fn bench_push_stack(bench: &mut Bencher) {
|
fn bench_push_stack(bench: &mut Bencher) {
|
||||||
bench.iter(|| {
|
bench.iter(|| {
|
||||||
let mut heap = MemoryArena::new();
|
let mut heap = MemoryArena::new();
|
||||||
let mut stacks = Vec::with_capacity(100);
|
let mut stacks: Vec<ExpUnrolledLinkedList> =
|
||||||
for _ in 0..NUM_STACK {
|
iter::repeat_with(ExpUnrolledLinkedList::new)
|
||||||
let mut stack = ExpUnrolledLinkedList::new();
|
.take(NUM_STACK)
|
||||||
stacks.push(stack);
|
.collect();
|
||||||
}
|
|
||||||
for s in 0..NUM_STACK {
|
for s in 0..NUM_STACK {
|
||||||
for i in 0u32..STACK_SIZE {
|
for i in 0u32..STACK_SIZE {
|
||||||
let t = s * 392017 % NUM_STACK;
|
let t = s * 392017 % NUM_STACK;
|
||||||
|
|||||||
@@ -130,5 +130,4 @@ mod tests {
|
|||||||
assert!(!scorer.advance());
|
assert!(!scorer.advance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,15 +8,13 @@ use crate::termdict::{TermDictionary, TermStreamer};
|
|||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use crate::TantivyError;
|
use crate::TantivyError;
|
||||||
use crate::{Result, SkipResult};
|
use crate::{Result, SkipResult};
|
||||||
|
use std::sync::Arc;
|
||||||
use tantivy_fst::Automaton;
|
use tantivy_fst::Automaton;
|
||||||
|
|
||||||
/// A weight struct for Fuzzy Term and Regex Queries
|
/// A weight struct for Fuzzy Term and Regex Queries
|
||||||
pub struct AutomatonWeight<A>
|
pub struct AutomatonWeight<A> {
|
||||||
where
|
|
||||||
A: Automaton + Send + Sync + 'static,
|
|
||||||
{
|
|
||||||
field: Field,
|
field: Field,
|
||||||
automaton: A,
|
automaton: Arc<A>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<A> AutomatonWeight<A>
|
impl<A> AutomatonWeight<A>
|
||||||
@@ -24,12 +22,16 @@ where
|
|||||||
A: Automaton + Send + Sync + 'static,
|
A: Automaton + Send + Sync + 'static,
|
||||||
{
|
{
|
||||||
/// Create a new AutomationWeight
|
/// Create a new AutomationWeight
|
||||||
pub fn new(field: Field, automaton: A) -> AutomatonWeight<A> {
|
pub fn new<IntoArcA: Into<Arc<A>>>(field: Field, automaton: IntoArcA) -> AutomatonWeight<A> {
|
||||||
AutomatonWeight { field, automaton }
|
AutomatonWeight {
|
||||||
|
field,
|
||||||
|
automaton: automaton.into(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn automaton_stream<'a>(&'a self, term_dict: &'a TermDictionary) -> TermStreamer<'a, &'a A> {
|
fn automaton_stream<'a>(&'a self, term_dict: &'a TermDictionary) -> TermStreamer<'a, &'a A> {
|
||||||
let term_stream_builder = term_dict.search(&self.automaton);
|
let automaton: &A = &*self.automaton;
|
||||||
|
let term_stream_builder = term_dict.search(automaton);
|
||||||
term_stream_builder.into_stream()
|
term_stream_builder.into_stream()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -216,5 +216,48 @@ mod tests {
|
|||||||
assert!(!docset.advance());
|
assert!(!docset.advance());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
|
mod bench {
|
||||||
|
|
||||||
|
use super::BitSet;
|
||||||
|
use super::BitSetDocSet;
|
||||||
|
use crate::test;
|
||||||
|
use crate::tests;
|
||||||
|
use crate::DocSet;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_bitset_1pct_insert(b: &mut test::Bencher) {
|
||||||
|
let els = tests::generate_nonunique_unsorted(1_000_000u32, 10_000);
|
||||||
|
b.iter(|| {
|
||||||
|
let mut bitset = BitSet::with_max_value(1_000_000);
|
||||||
|
for el in els.iter().cloned() {
|
||||||
|
bitset.insert(el);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_bitset_1pct_clone(b: &mut test::Bencher) {
|
||||||
|
let els = tests::generate_nonunique_unsorted(1_000_000u32, 10_000);
|
||||||
|
let mut bitset = BitSet::with_max_value(1_000_000);
|
||||||
|
for el in els {
|
||||||
|
bitset.insert(el);
|
||||||
|
}
|
||||||
|
b.iter(|| bitset.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_bitset_1pct_clone_iterate(b: &mut test::Bencher) {
|
||||||
|
let els = tests::sample(1_000_000u32, 0.01);
|
||||||
|
let mut bitset = BitSet::with_max_value(1_000_000);
|
||||||
|
for el in els {
|
||||||
|
bitset.insert(el);
|
||||||
|
}
|
||||||
|
b.iter(|| {
|
||||||
|
let mut docset = BitSetDocSet::from(bitset.clone());
|
||||||
|
while docset.advance() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -137,5 +137,4 @@ mod tests {
|
|||||||
fn test_idf() {
|
fn test_idf() {
|
||||||
assert_nearly_equals(idf(1, 2), 0.6931472);
|
assert_nearly_equals(idf(1, 2), 0.6931472);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,9 +247,7 @@ mod tests {
|
|||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
let query_parser = QueryParser::for_index(&index, vec![title, text]);
|
let query_parser = QueryParser::for_index(&index, vec![title, text]);
|
||||||
let query = query_parser
|
let query = query_parser.parse_query("Оксана Лифенко").unwrap();
|
||||||
.parse_query("Оксана Лифенко")
|
|
||||||
.unwrap();
|
|
||||||
let weight = query.weight(&searcher, true).unwrap();
|
let weight = query.weight(&searcher, true).unwrap();
|
||||||
let mut scorer = weight.scorer(searcher.segment_reader(0u32)).unwrap();
|
let mut scorer = weight.scorer(searcher.segment_reader(0u32)).unwrap();
|
||||||
scorer.advance();
|
scorer.advance();
|
||||||
|
|||||||
@@ -175,5 +175,4 @@ mod tests {
|
|||||||
sample_skip,
|
sample_skip,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,12 +28,10 @@ static LEV_BUILDER: Lazy<HashMap<(u8, bool), LevenshteinAutomatonBuilder>> = Laz
|
|||||||
/// containing a specific term that is within
|
/// containing a specific term that is within
|
||||||
/// Levenshtein distance
|
/// Levenshtein distance
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result, Term};
|
|
||||||
/// use tantivy::collector::{Count, TopDocs};
|
/// use tantivy::collector::{Count, TopDocs};
|
||||||
/// use tantivy::query::FuzzyTermQuery;
|
/// use tantivy::query::FuzzyTermQuery;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, Index, Result, Term};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ pub fn intersect_scorers(mut scorers: Vec<Box<dyn Scorer>>) -> Box<dyn Scorer> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a `DocSet` that iterator through the intersection of two `DocSet`s.
|
/// Creates a `DocSet` that iterate through the intersection of two or more `DocSet`s.
|
||||||
pub struct Intersection<TDocSet: DocSet, TOtherDocSet: DocSet = Box<dyn Scorer>> {
|
pub struct Intersection<TDocSet: DocSet, TOtherDocSet: DocSet = Box<dyn Scorer>> {
|
||||||
left: TDocSet,
|
left: TDocSet,
|
||||||
right: TDocSet,
|
right: TDocSet,
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use Score;
|
|||||||
use SkipResult;
|
use SkipResult;
|
||||||
|
|
||||||
|
|
||||||
/// Creates a `DocSet` that iterator through the intersection of two `DocSet`s.
|
/// Creates a `DocSet` that iterate through the intersection of two `DocSet`s.
|
||||||
pub struct IntersectionTwoTerms<TDocSet> {
|
pub struct IntersectionTwoTerms<TDocSet> {
|
||||||
left: TDocSet,
|
left: TDocSet,
|
||||||
right: TDocSet
|
right: TDocSet
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ 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;
|
||||||
@@ -43,7 +42,6 @@ pub use self::exclude::Exclude;
|
|||||||
pub use self::explanation::Explanation;
|
pub use self::explanation::Explanation;
|
||||||
pub use self::fuzzy_query::FuzzyTermQuery;
|
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;
|
||||||
@@ -55,6 +53,7 @@ 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 {
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
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,9 +1,5 @@
|
|||||||
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;
|
||||||
@@ -16,11 +12,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)]
|
||||||
@@ -222,9 +218,8 @@ 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, _remaining) = parse_to_ast()
|
let user_input_ast =
|
||||||
.parse(query)
|
tantivy_query_grammar::parse_query(query).map_err(|_| QueryParserError::SyntaxError)?;
|
||||||
.map_err(|_| QueryParserError::SyntaxError)?;
|
|
||||||
self.compute_logical_ast(user_input_ast)
|
self.compute_logical_ast(user_input_ast)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -369,6 +364,7 @@ impl QueryParser {
|
|||||||
match *bound {
|
match *bound {
|
||||||
UserInputBound::Inclusive(_) => Ok(Bound::Included(term)),
|
UserInputBound::Inclusive(_) => Ok(Bound::Included(term)),
|
||||||
UserInputBound::Exclusive(_) => Ok(Bound::Excluded(term)),
|
UserInputBound::Exclusive(_) => Ok(Bound::Excluded(term)),
|
||||||
|
UserInputBound::Unbounded => Ok(Bound::Unbounded),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -398,7 +394,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 = compose_occur(default_occur, occur);
|
let new_occur = Occur::compose(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)))
|
||||||
@@ -406,7 +402,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((compose_occur(left_occur, right_occur), logical_sub_queries))
|
Ok((Occur::compose(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)?;
|
||||||
@@ -628,7 +624,7 @@ mod test {
|
|||||||
pub fn test_parse_query_untokenized() {
|
pub fn test_parse_query_untokenized() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"nottokenized:\"wordone wordtwo\"",
|
"nottokenized:\"wordone wordtwo\"",
|
||||||
"Term([0, 0, 0, 7, 119, 111, 114, 100, 111, 110, \
|
"Term(field=7,bytes=[119, 111, 114, 100, 111, 110, \
|
||||||
101, 32, 119, 111, 114, 100, 116, 119, 111])",
|
101, 32, 119, 111, 114, 100, 116, 119, 111])",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
@@ -672,7 +668,7 @@ mod test {
|
|||||||
.is_ok());
|
.is_ok());
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"unsigned:2324",
|
"unsigned:2324",
|
||||||
"Term([0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 9, 20])",
|
"Term(field=3,bytes=[0, 0, 0, 0, 0, 0, 9, 20])",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -693,19 +689,19 @@ mod test {
|
|||||||
pub fn test_parse_query_to_ast_single_term() {
|
pub fn test_parse_query_to_ast_single_term() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:toto",
|
"title:toto",
|
||||||
"Term([0, 0, 0, 0, 116, 111, 116, 111])",
|
"Term(field=0,bytes=[116, 111, 116, 111])",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"+title:toto",
|
"+title:toto",
|
||||||
"Term([0, 0, 0, 0, 116, 111, 116, 111])",
|
"Term(field=0,bytes=[116, 111, 116, 111])",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"+title:toto -titi",
|
"+title:toto -titi",
|
||||||
"(+Term([0, 0, 0, 0, 116, 111, 116, 111]) \
|
"(+Term(field=0,bytes=[116, 111, 116, 111]) \
|
||||||
-(Term([0, 0, 0, 0, 116, 105, 116, 105]) \
|
-(Term(field=0,bytes=[116, 105, 116, 105]) \
|
||||||
Term([0, 0, 0, 1, 116, 105, 116, 105])))",
|
Term(field=1,bytes=[116, 105, 116, 105])))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -720,14 +716,13 @@ mod test {
|
|||||||
pub fn test_parse_query_to_ast_two_terms() {
|
pub fn test_parse_query_to_ast_two_terms() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:a b",
|
"title:a b",
|
||||||
"(Term([0, 0, 0, 0, 97]) (Term([0, 0, 0, 0, 98]) \
|
"(Term(field=0,bytes=[97]) (Term(field=0,bytes=[98]) Term(field=1,bytes=[98])))",
|
||||||
Term([0, 0, 0, 1, 98])))",
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:\"a b\"",
|
"title:\"a b\"",
|
||||||
"\"[(0, Term([0, 0, 0, 0, 97])), \
|
"\"[(0, Term(field=0,bytes=[97])), \
|
||||||
(1, Term([0, 0, 0, 0, 98]))]\"",
|
(1, Term(field=0,bytes=[98]))]\"",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -736,45 +731,43 @@ mod test {
|
|||||||
pub fn test_parse_query_to_ast_ranges() {
|
pub fn test_parse_query_to_ast_ranges() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:[a TO b]",
|
"title:[a TO b]",
|
||||||
"(Included(Term([0, 0, 0, 0, 97])) TO \
|
"(Included(Term(field=0,bytes=[97])) TO Included(Term(field=0,bytes=[98])))",
|
||||||
Included(Term([0, 0, 0, 0, 98])))",
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"[a TO b]",
|
"[a TO b]",
|
||||||
"((Included(Term([0, 0, 0, 0, 97])) TO \
|
"((Included(Term(field=0,bytes=[97])) TO \
|
||||||
Included(Term([0, 0, 0, 0, 98]))) \
|
Included(Term(field=0,bytes=[98]))) \
|
||||||
(Included(Term([0, 0, 0, 1, 97])) TO \
|
(Included(Term(field=1,bytes=[97])) TO \
|
||||||
Included(Term([0, 0, 0, 1, 98]))))",
|
Included(Term(field=1,bytes=[98]))))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:{titi TO toto}",
|
"title:{titi TO toto}",
|
||||||
"(Excluded(Term([0, 0, 0, 0, 116, 105, 116, 105])) TO \
|
"(Excluded(Term(field=0,bytes=[116, 105, 116, 105])) TO \
|
||||||
Excluded(Term([0, 0, 0, 0, 116, 111, 116, 111])))",
|
Excluded(Term(field=0,bytes=[116, 111, 116, 111])))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:{* TO toto}",
|
"title:{* TO toto}",
|
||||||
"(Unbounded TO \
|
"(Unbounded TO Excluded(Term(field=0,bytes=[116, 111, 116, 111])))",
|
||||||
Excluded(Term([0, 0, 0, 0, 116, 111, 116, 111])))",
|
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:{titi TO *}",
|
"title:{titi TO *}",
|
||||||
"(Excluded(Term([0, 0, 0, 0, 116, 105, 116, 105])) TO Unbounded)",
|
"(Excluded(Term(field=0,bytes=[116, 105, 116, 105])) TO Unbounded)",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"signed:{-5 TO 3}",
|
"signed:{-5 TO 3}",
|
||||||
"(Excluded(Term([0, 0, 0, 2, 127, 255, 255, 255, 255, 255, 255, 251])) TO \
|
"(Excluded(Term(field=2,bytes=[127, 255, 255, 255, 255, 255, 255, 251])) TO \
|
||||||
Excluded(Term([0, 0, 0, 2, 128, 0, 0, 0, 0, 0, 0, 3])))",
|
Excluded(Term(field=2,bytes=[128, 0, 0, 0, 0, 0, 0, 3])))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"float:{-1.5 TO 1.5}",
|
"float:{-1.5 TO 1.5}",
|
||||||
"(Excluded(Term([0, 0, 0, 10, 64, 7, 255, 255, 255, 255, 255, 255])) TO \
|
"(Excluded(Term(field=10,bytes=[64, 7, 255, 255, 255, 255, 255, 255])) TO \
|
||||||
Excluded(Term([0, 0, 0, 10, 191, 248, 0, 0, 0, 0, 0, 0])))",
|
Excluded(Term(field=10,bytes=[191, 248, 0, 0, 0, 0, 0, 0])))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -879,19 +872,19 @@ mod test {
|
|||||||
pub fn test_parse_query_to_ast_conjunction() {
|
pub fn test_parse_query_to_ast_conjunction() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:toto",
|
"title:toto",
|
||||||
"Term([0, 0, 0, 0, 116, 111, 116, 111])",
|
"Term(field=0,bytes=[116, 111, 116, 111])",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"+title:toto",
|
"+title:toto",
|
||||||
"Term([0, 0, 0, 0, 116, 111, 116, 111])",
|
"Term(field=0,bytes=[116, 111, 116, 111])",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"+title:toto -titi",
|
"+title:toto -titi",
|
||||||
"(+Term([0, 0, 0, 0, 116, 111, 116, 111]) \
|
"(+Term(field=0,bytes=[116, 111, 116, 111]) \
|
||||||
-(Term([0, 0, 0, 0, 116, 105, 116, 105]) \
|
-(Term(field=0,bytes=[116, 105, 116, 105]) \
|
||||||
Term([0, 0, 0, 1, 116, 105, 116, 105])))",
|
Term(field=1,bytes=[116, 105, 116, 105])))",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -902,15 +895,15 @@ mod test {
|
|||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:a b",
|
"title:a b",
|
||||||
"(+Term([0, 0, 0, 0, 97]) \
|
"(+Term(field=0,bytes=[97]) \
|
||||||
+(Term([0, 0, 0, 0, 98]) \
|
+(Term(field=0,bytes=[98]) \
|
||||||
Term([0, 0, 0, 1, 98])))",
|
Term(field=1,bytes=[98])))",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:\"a b\"",
|
"title:\"a b\"",
|
||||||
"\"[(0, Term([0, 0, 0, 0, 97])), \
|
"\"[(0, Term(field=0,bytes=[97])), \
|
||||||
(1, Term([0, 0, 0, 0, 98]))]\"",
|
(1, Term(field=0,bytes=[98]))]\"",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -919,10 +912,8 @@ mod test {
|
|||||||
pub fn test_query_parser_hyphen() {
|
pub fn test_query_parser_hyphen() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:www-form-encoded",
|
"title:www-form-encoded",
|
||||||
"\"[(0, Term([0, 0, 0, 0, 119, 119, 119])), \
|
"\"[(0, Term(field=0,bytes=[119, 119, 119])), (1, Term(field=0,bytes=[102, 111, 114, 109])), (2, Term(field=0,bytes=[101, 110, 99, 111, 100, 101, 100]))]\"",
|
||||||
(1, Term([0, 0, 0, 0, 102, 111, 114, 109])), \
|
false
|
||||||
(2, Term([0, 0, 0, 0, 101, 110, 99, 111, 100, 101, 100]))]\"",
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,14 +38,10 @@ fn map_bound<TFrom, TTo, Transform: Fn(&TFrom) -> TTo>(
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
///
|
|
||||||
/// # #[macro_use]
|
|
||||||
/// # extern crate tantivy;
|
|
||||||
/// # use tantivy::Index;
|
|
||||||
/// # use tantivy::schema::{Schema, INDEXED};
|
|
||||||
/// # use tantivy::collector::Count;
|
/// # use tantivy::collector::Count;
|
||||||
/// # use tantivy::Result;
|
|
||||||
/// # use tantivy::query::RangeQuery;
|
/// # use tantivy::query::RangeQuery;
|
||||||
|
/// # use tantivy::schema::{Schema, INDEXED};
|
||||||
|
/// # use tantivy::{doc, Index, Result};
|
||||||
/// #
|
/// #
|
||||||
/// # fn run() -> Result<()> {
|
/// # fn run() -> Result<()> {
|
||||||
/// # let mut schema_builder = Schema::builder();
|
/// # let mut schema_builder = Schema::builder();
|
||||||
@@ -338,39 +334,33 @@ mod tests {
|
|||||||
use crate::collector::Count;
|
use crate::collector::Count;
|
||||||
use crate::schema::{Document, Field, Schema, INDEXED};
|
use crate::schema::{Document, Field, Schema, INDEXED};
|
||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::Result;
|
|
||||||
use std::collections::Bound;
|
use std::collections::Bound;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_range_query_simple() {
|
fn test_range_query_simple() {
|
||||||
fn run() -> Result<()> {
|
let mut schema_builder = Schema::builder();
|
||||||
let mut schema_builder = Schema::builder();
|
let year_field = schema_builder.add_u64_field("year", INDEXED);
|
||||||
let year_field = schema_builder.add_u64_field("year", INDEXED);
|
let schema = schema_builder.build();
|
||||||
let schema = schema_builder.build();
|
|
||||||
|
|
||||||
let index = Index::create_in_ram(schema);
|
let index = Index::create_in_ram(schema);
|
||||||
{
|
{
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 6_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 6_000_000).unwrap();
|
||||||
for year in 1950u64..2017u64 {
|
for year in 1950u64..2017u64 {
|
||||||
let num_docs_within_year = 10 + (year - 1950) * (year - 1950);
|
let num_docs_within_year = 10 + (year - 1950) * (year - 1950);
|
||||||
for _ in 0..num_docs_within_year {
|
for _ in 0..num_docs_within_year {
|
||||||
index_writer.add_document(doc!(year_field => year));
|
index_writer.add_document(doc!(year_field => year));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
index_writer.commit().unwrap();
|
|
||||||
}
|
}
|
||||||
let reader = index.reader().unwrap();
|
index_writer.commit().unwrap();
|
||||||
let searcher = reader.searcher();
|
|
||||||
|
|
||||||
let docs_in_the_sixties = RangeQuery::new_u64(year_field, 1960u64..1970u64);
|
|
||||||
|
|
||||||
// ... or `1960..=1969` if inclusive range is enabled.
|
|
||||||
let count = searcher.search(&docs_in_the_sixties, &Count)?;
|
|
||||||
assert_eq!(count, 2285);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
let reader = index.reader().unwrap();
|
||||||
|
let searcher = reader.searcher();
|
||||||
|
|
||||||
run().unwrap();
|
let docs_in_the_sixties = RangeQuery::new_u64(year_field, 1960u64..1970u64);
|
||||||
|
|
||||||
|
// ... or `1960..=1969` if inclusive range is enabled.
|
||||||
|
let count = searcher.search(&docs_in_the_sixties, &Count).unwrap();
|
||||||
|
assert_eq!(count, 2285);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -489,5 +479,4 @@ mod tests {
|
|||||||
91
|
91
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,22 +4,18 @@ use crate::schema::Field;
|
|||||||
use crate::Result;
|
use crate::Result;
|
||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
use std::clone::Clone;
|
use std::clone::Clone;
|
||||||
|
use std::sync::Arc;
|
||||||
use tantivy_fst::Regex;
|
use tantivy_fst::Regex;
|
||||||
|
|
||||||
// A Regex Query matches all of the documents
|
/// A Regex Query matches all of the documents
|
||||||
/// containing a specific term that matches
|
/// containing a specific term that matches
|
||||||
/// a regex pattern
|
/// a regex pattern.
|
||||||
/// A Fuzzy Query matches all of the documents
|
|
||||||
/// containing a specific term that is within
|
|
||||||
/// Levenshtein distance
|
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// use tantivy::{Index, Result, Term};
|
|
||||||
/// use tantivy::collector::Count;
|
/// use tantivy::collector::Count;
|
||||||
/// use tantivy::query::RegexQuery;
|
/// use tantivy::query::RegexQuery;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// use tantivy::{doc, Index, Result, Term};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
@@ -48,7 +44,7 @@ use tantivy_fst::Regex;
|
|||||||
/// let searcher = reader.searcher();
|
/// let searcher = reader.searcher();
|
||||||
///
|
///
|
||||||
/// let term = Term::from_field_text(title, "Diary");
|
/// let term = Term::from_field_text(title, "Diary");
|
||||||
/// let query = RegexQuery::new("d[ai]{2}ry".to_string(), title);
|
/// let query = RegexQuery::from_pattern("d[ai]{2}ry", title)?;
|
||||||
/// let count = searcher.search(&query, &Count)?;
|
/// let count = searcher.search(&query, &Count)?;
|
||||||
/// assert_eq!(count, 3);
|
/// assert_eq!(count, 3);
|
||||||
/// Ok(())
|
/// Ok(())
|
||||||
@@ -56,30 +52,34 @@ use tantivy_fst::Regex;
|
|||||||
/// ```
|
/// ```
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct RegexQuery {
|
pub struct RegexQuery {
|
||||||
regex_pattern: String,
|
regex: Arc<Regex>,
|
||||||
field: Field,
|
field: Field,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RegexQuery {
|
impl RegexQuery {
|
||||||
/// Creates a new Fuzzy Query
|
/// Creates a new RegexQuery from a given pattern
|
||||||
pub fn new(regex_pattern: String, field: Field) -> RegexQuery {
|
pub fn from_pattern(regex_pattern: &str, field: Field) -> Result<Self> {
|
||||||
|
let regex = Regex::new(®ex_pattern)
|
||||||
|
.map_err(|_| TantivyError::InvalidArgument(regex_pattern.to_string()))?;
|
||||||
|
Ok(RegexQuery::from_regex(regex, field))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new RegexQuery from a fully built Regex
|
||||||
|
pub fn from_regex<T: Into<Arc<Regex>>>(regex: T, field: Field) -> Self {
|
||||||
RegexQuery {
|
RegexQuery {
|
||||||
regex_pattern,
|
regex: regex.into(),
|
||||||
field,
|
field,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn specialized_weight(&self) -> Result<AutomatonWeight<Regex>> {
|
fn specialized_weight(&self) -> AutomatonWeight<Regex> {
|
||||||
let automaton = Regex::new(&self.regex_pattern)
|
AutomatonWeight::new(self.field, self.regex.clone())
|
||||||
.map_err(|_| TantivyError::InvalidArgument(self.regex_pattern.clone()))?;
|
|
||||||
|
|
||||||
Ok(AutomatonWeight::new(self.field, automaton))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Query for RegexQuery {
|
impl Query for RegexQuery {
|
||||||
fn weight(&self, _searcher: &Searcher, _scoring_enabled: bool) -> Result<Box<dyn Weight>> {
|
fn weight(&self, _searcher: &Searcher, _scoring_enabled: bool) -> Result<Box<dyn Weight>> {
|
||||||
Ok(Box::new(self.specialized_weight()?))
|
Ok(Box::new(self.specialized_weight()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,13 +87,14 @@ impl Query for RegexQuery {
|
|||||||
mod test {
|
mod test {
|
||||||
use super::RegexQuery;
|
use super::RegexQuery;
|
||||||
use crate::collector::TopDocs;
|
use crate::collector::TopDocs;
|
||||||
use crate::schema::Schema;
|
|
||||||
use crate::schema::TEXT;
|
use crate::schema::TEXT;
|
||||||
|
use crate::schema::{Field, Schema};
|
||||||
use crate::tests::assert_nearly_equals;
|
use crate::tests::assert_nearly_equals;
|
||||||
use crate::Index;
|
use crate::{Index, IndexReader};
|
||||||
|
use std::sync::Arc;
|
||||||
|
use tantivy_fst::Regex;
|
||||||
|
|
||||||
#[test]
|
fn build_test_index() -> (IndexReader, Field) {
|
||||||
pub fn test_regex_query() {
|
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
let country_field = schema_builder.add_text_field("country", TEXT);
|
let country_field = schema_builder.add_text_field("country", TEXT);
|
||||||
let schema = schema_builder.build();
|
let schema = schema_builder.build();
|
||||||
@@ -109,20 +110,65 @@ mod test {
|
|||||||
index_writer.commit().unwrap();
|
index_writer.commit().unwrap();
|
||||||
}
|
}
|
||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
|
|
||||||
|
(reader, country_field)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn verify_regex_query(
|
||||||
|
query_matching_one: RegexQuery,
|
||||||
|
query_matching_zero: RegexQuery,
|
||||||
|
reader: IndexReader,
|
||||||
|
) {
|
||||||
let searcher = reader.searcher();
|
let searcher = reader.searcher();
|
||||||
{
|
{
|
||||||
let regex_query = RegexQuery::new("jap[ao]n".to_string(), country_field);
|
|
||||||
let scored_docs = searcher
|
let scored_docs = searcher
|
||||||
.search(®ex_query, &TopDocs::with_limit(2))
|
.search(&query_matching_one, &TopDocs::with_limit(2))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(scored_docs.len(), 1, "Expected only 1 document");
|
assert_eq!(scored_docs.len(), 1, "Expected only 1 document");
|
||||||
let (score, _) = scored_docs[0];
|
let (score, _) = scored_docs[0];
|
||||||
assert_nearly_equals(1f32, score);
|
assert_nearly_equals(1f32, score);
|
||||||
}
|
}
|
||||||
let regex_query = RegexQuery::new("jap[A-Z]n".to_string(), country_field);
|
|
||||||
let top_docs = searcher
|
let top_docs = searcher
|
||||||
.search(®ex_query, &TopDocs::with_limit(2))
|
.search(&query_matching_zero, &TopDocs::with_limit(2))
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(top_docs.is_empty(), "Expected ZERO document");
|
assert!(top_docs.is_empty(), "Expected ZERO document");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_regex_query() {
|
||||||
|
let (reader, field) = build_test_index();
|
||||||
|
|
||||||
|
let matching_one = RegexQuery::from_pattern("jap[ao]n", field).unwrap();
|
||||||
|
let matching_zero = RegexQuery::from_pattern("jap[A-Z]n", field).unwrap();
|
||||||
|
|
||||||
|
verify_regex_query(matching_one, matching_zero, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_construct_from_regex() {
|
||||||
|
let (reader, field) = build_test_index();
|
||||||
|
|
||||||
|
let matching_one = RegexQuery::from_regex(Regex::new("jap[ao]n").unwrap(), field);
|
||||||
|
let matching_zero = RegexQuery::from_regex(Regex::new("jap[A-Z]n").unwrap(), field);
|
||||||
|
|
||||||
|
verify_regex_query(matching_one, matching_zero, reader);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_construct_from_reused_regex() {
|
||||||
|
let r1 = Arc::new(Regex::new("jap[ao]n").unwrap());
|
||||||
|
let r2 = Arc::new(Regex::new("jap[A-Z]n").unwrap());
|
||||||
|
|
||||||
|
let (reader, field) = build_test_index();
|
||||||
|
|
||||||
|
let matching_one = RegexQuery::from_regex(r1.clone(), field);
|
||||||
|
let matching_zero = RegexQuery::from_regex(r2.clone(), field);
|
||||||
|
|
||||||
|
verify_regex_query(matching_one, matching_zero, reader.clone());
|
||||||
|
|
||||||
|
let matching_one = RegexQuery::from_regex(r1.clone(), field);
|
||||||
|
let matching_zero = RegexQuery::from_regex(r2.clone(), field);
|
||||||
|
|
||||||
|
verify_regex_query(matching_one, matching_zero, reader.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -190,5 +190,4 @@ mod tests {
|
|||||||
skip_docs,
|
skip_docs,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ mod tests {
|
|||||||
use crate::collector::TopDocs;
|
use crate::collector::TopDocs;
|
||||||
use crate::docset::DocSet;
|
use crate::docset::DocSet;
|
||||||
use crate::query::{Query, QueryParser, Scorer, TermQuery};
|
use crate::query::{Query, QueryParser, Scorer, TermQuery};
|
||||||
use crate::schema::{IndexRecordOption, Schema, STRING, TEXT};
|
use crate::schema::{Field, IndexRecordOption, Schema, STRING, TEXT};
|
||||||
use crate::tests::assert_nearly_equals;
|
use crate::tests::assert_nearly_equals;
|
||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::Term;
|
use crate::Term;
|
||||||
@@ -114,4 +114,16 @@ mod tests {
|
|||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
assert_eq!(term_query.count(&*reader.searcher()).unwrap(), 1);
|
assert_eq!(term_query.count(&*reader.searcher()).unwrap(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_term_query_debug() {
|
||||||
|
let term_query = TermQuery::new(
|
||||||
|
Term::from_field_text(Field(1), "hello"),
|
||||||
|
IndexRecordOption::WithFreqs,
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
format!("{:?}", term_query),
|
||||||
|
"TermQuery(Term(field=1,bytes=[104, 101, 108, 108, 111]))"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ use crate::Result;
|
|||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
use crate::Term;
|
use crate::Term;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
/// A Term query matches all of the documents
|
/// A Term query matches all of the documents
|
||||||
/// containing a specific term.
|
/// containing a specific term.
|
||||||
@@ -19,12 +20,10 @@ use std::collections::BTreeSet;
|
|||||||
/// * `field norm` - number of tokens in the field.
|
/// * `field norm` - number of tokens in the field.
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// #[macro_use]
|
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::schema::{Schema, TEXT, IndexRecordOption};
|
|
||||||
/// use tantivy::{Index, Result, Term};
|
|
||||||
/// use tantivy::collector::{Count, TopDocs};
|
/// use tantivy::collector::{Count, TopDocs};
|
||||||
/// use tantivy::query::TermQuery;
|
/// use tantivy::query::TermQuery;
|
||||||
|
/// use tantivy::schema::{Schema, TEXT, IndexRecordOption};
|
||||||
|
/// use tantivy::{doc, Index, Result, Term};
|
||||||
///
|
///
|
||||||
/// # fn main() { example().unwrap(); }
|
/// # fn main() { example().unwrap(); }
|
||||||
/// fn example() -> Result<()> {
|
/// fn example() -> Result<()> {
|
||||||
@@ -61,12 +60,18 @@ use std::collections::BTreeSet;
|
|||||||
/// Ok(())
|
/// Ok(())
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone)]
|
||||||
pub struct TermQuery {
|
pub struct TermQuery {
|
||||||
term: Term,
|
term: Term,
|
||||||
index_record_option: IndexRecordOption,
|
index_record_option: IndexRecordOption,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for TermQuery {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
write!(f, "TermQuery({:?})", self.term)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl TermQuery {
|
impl TermQuery {
|
||||||
/// Creates a new term query.
|
/// Creates a new term query.
|
||||||
pub fn new(term: Term, segment_postings_options: IndexRecordOption) -> TermQuery {
|
pub fn new(term: Term, segment_postings_options: IndexRecordOption) -> TermQuery {
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a `DocSet` that iterator through the intersection of two `DocSet`s.
|
/// Creates a `DocSet` that iterate through the union of two or more `DocSet`s.
|
||||||
pub struct Union<TScorer, TScoreCombiner = DoNothingCombiner> {
|
pub struct Union<TScorer, TScoreCombiner = DoNothingCombiner> {
|
||||||
docsets: Vec<TScorer>,
|
docsets: Vec<TScorer>,
|
||||||
bitsets: Box<[TinySet; HORIZON_NUM_TINYBITSETS]>,
|
bitsets: Box<[TinySet; HORIZON_NUM_TINYBITSETS]>,
|
||||||
@@ -409,5 +409,51 @@ mod tests {
|
|||||||
vec![1, 2, 3, 7, 8, 9, 99, 100, 101, 500, 20000],
|
vec![1, 2, 3, 7, 8, 9, 99, 100, 101, 500, 20000],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
|
mod bench {
|
||||||
|
|
||||||
|
use crate::query::score_combiner::DoNothingCombiner;
|
||||||
|
use crate::query::{ConstScorer, Union, VecDocSet};
|
||||||
|
use crate::tests;
|
||||||
|
use crate::DocId;
|
||||||
|
use crate::DocSet;
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn bench_union_3_high(bench: &mut Bencher) {
|
||||||
|
let union_docset: Vec<Vec<DocId>> = vec![
|
||||||
|
tests::sample_with_seed(100_000, 0.1, 0),
|
||||||
|
tests::sample_with_seed(100_000, 0.2, 1),
|
||||||
|
];
|
||||||
|
bench.iter(|| {
|
||||||
|
let mut v = Union::<_, DoNothingCombiner>::from(
|
||||||
|
union_docset
|
||||||
|
.iter()
|
||||||
|
.map(|doc_ids| VecDocSet::from(doc_ids.clone()))
|
||||||
|
.map(ConstScorer::new)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
while v.advance() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
#[bench]
|
||||||
|
fn bench_union_3_low(bench: &mut Bencher) {
|
||||||
|
let union_docset: Vec<Vec<DocId>> = vec![
|
||||||
|
tests::sample_with_seed(100_000, 0.01, 0),
|
||||||
|
tests::sample_with_seed(100_000, 0.05, 1),
|
||||||
|
tests::sample_with_seed(100_000, 0.001, 2),
|
||||||
|
];
|
||||||
|
bench.iter(|| {
|
||||||
|
let mut v = Union::<_, DoNothingCombiner>::from(
|
||||||
|
union_docset
|
||||||
|
.iter()
|
||||||
|
.map(|doc_ids| VecDocSet::from(doc_ids.clone()))
|
||||||
|
.map(ConstScorer::new)
|
||||||
|
.collect::<Vec<_>>(),
|
||||||
|
);
|
||||||
|
while v.advance() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,5 +82,4 @@ pub mod tests {
|
|||||||
}
|
}
|
||||||
assert_eq!(postings.fill_buffer(&mut buffer[..]), 9);
|
assert_eq!(postings.fill_buffer(&mut buffer[..]), 9);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,5 +178,4 @@ mod tests {
|
|||||||
doc.add_text(text_field, "My title");
|
doc.add_text(text_field, "My title");
|
||||||
assert_eq!(doc.field_values().len(), 1);
|
assert_eq!(doc.field_values().len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ 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
|
||||||
/// At this point the JSON is known to be valid.
|
/// At this point the JSON is known to be valid.
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub enum ValueParsingError {
|
pub enum ValueParsingError {
|
||||||
/// Encountered a numerical value that overflows or underflow its integer type.
|
/// Encountered a numerical value that overflows or underflow its integer type.
|
||||||
OverflowError(String),
|
OverflowError(String),
|
||||||
|
|||||||
@@ -29,22 +29,6 @@ pub enum IndexRecordOption {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl IndexRecordOption {
|
impl IndexRecordOption {
|
||||||
/// Returns true iff the term frequency will be encoded.
|
|
||||||
pub fn is_termfreq_enabled(self) -> bool {
|
|
||||||
match self {
|
|
||||||
IndexRecordOption::WithFreqsAndPositions | IndexRecordOption::WithFreqs => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true iff the term positions within the document are stored as well.
|
|
||||||
pub fn is_position_enabled(self) -> bool {
|
|
||||||
match self {
|
|
||||||
IndexRecordOption::WithFreqsAndPositions => true,
|
|
||||||
_ => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns true iff this option includes encoding
|
/// Returns true iff this option includes encoding
|
||||||
/// term frequencies.
|
/// term frequencies.
|
||||||
pub fn has_freq(self) -> bool {
|
pub fn has_freq(self) -> bool {
|
||||||
|
|||||||
@@ -174,5 +174,4 @@ mod tests {
|
|||||||
assert!(!is_valid_field_name("シャボン玉"));
|
assert!(!is_valid_field_name("シャボン玉"));
|
||||||
assert!(is_valid_field_name("my_text_field"));
|
assert!(is_valid_field_name("my_text_field"));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,6 +246,25 @@ impl Schema {
|
|||||||
self.0.fields_map.get(field_name).cloned()
|
self.0.fields_map.get(field_name).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create a named document off the doc.
|
||||||
|
pub fn convert_named_doc(
|
||||||
|
&self,
|
||||||
|
named_doc: NamedFieldDocument,
|
||||||
|
) -> Result<Document, DocParsingError> {
|
||||||
|
let mut document = Document::new();
|
||||||
|
for (field_name, values) in named_doc.0 {
|
||||||
|
if let Some(field) = self.get_field(&field_name) {
|
||||||
|
for value in values {
|
||||||
|
let field_value = FieldValue::new(field, value);
|
||||||
|
document.add(field_value);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Err(DocParsingError::NoSuchFieldInSchema(field_name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(document)
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a named document off the doc.
|
/// Create a named document off the doc.
|
||||||
pub fn to_named_doc(&self, doc: &Document) -> NamedFieldDocument {
|
pub fn to_named_doc(&self, doc: &Document) -> NamedFieldDocument {
|
||||||
let mut field_map = BTreeMap::new();
|
let mut field_map = BTreeMap::new();
|
||||||
@@ -282,28 +301,26 @@ impl Schema {
|
|||||||
|
|
||||||
let mut doc = Document::default();
|
let mut doc = Document::default();
|
||||||
for (field_name, json_value) in json_obj.iter() {
|
for (field_name, json_value) in json_obj.iter() {
|
||||||
match self.get_field(field_name) {
|
let field = self
|
||||||
Some(field) => {
|
.get_field(field_name)
|
||||||
let field_entry = self.get_field_entry(field);
|
.ok_or_else(|| DocParsingError::NoSuchFieldInSchema(field_name.clone()))?;
|
||||||
let field_type = field_entry.field_type();
|
let field_entry = self.get_field_entry(field);
|
||||||
match *json_value {
|
let field_type = field_entry.field_type();
|
||||||
JsonValue::Array(ref json_items) => {
|
match *json_value {
|
||||||
for json_item in json_items {
|
JsonValue::Array(ref json_items) => {
|
||||||
let value = field_type.value_from_json(json_item).map_err(|e| {
|
for json_item in json_items {
|
||||||
DocParsingError::ValueError(field_name.clone(), e)
|
let value = field_type
|
||||||
})?;
|
.value_from_json(json_item)
|
||||||
doc.add(FieldValue::new(field, value));
|
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))?;
|
||||||
}
|
doc.add(FieldValue::new(field, value));
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
let value = field_type
|
|
||||||
.value_from_json(json_value)
|
|
||||||
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))?;
|
|
||||||
doc.add(FieldValue::new(field, value));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => return Err(DocParsingError::NoSuchFieldInSchema(field_name.clone())),
|
_ => {
|
||||||
|
let value = field_type
|
||||||
|
.value_from_json(json_value)
|
||||||
|
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))?;
|
||||||
|
doc.add(FieldValue::new(field, value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(doc)
|
Ok(doc)
|
||||||
@@ -360,7 +377,7 @@ impl<'de> Deserialize<'de> for Schema {
|
|||||||
|
|
||||||
/// Error that may happen when deserializing
|
/// Error that may happen when deserializing
|
||||||
/// a document from JSON.
|
/// a document from JSON.
|
||||||
#[derive(Debug, Fail)]
|
#[derive(Debug, Fail, PartialEq)]
|
||||||
pub enum DocParsingError {
|
pub enum DocParsingError {
|
||||||
/// The payload given is not valid JSON.
|
/// The payload given is not valid JSON.
|
||||||
#[fail(display = "The provided string is not valid JSON")]
|
#[fail(display = "The provided string is not valid JSON")]
|
||||||
@@ -369,7 +386,10 @@ pub enum DocParsingError {
|
|||||||
#[fail(display = "The field '{:?}' could not be parsed: {:?}", _0, _1)]
|
#[fail(display = "The field '{:?}' could not be parsed: {:?}", _0, _1)]
|
||||||
ValueError(String, ValueParsingError),
|
ValueError(String, ValueParsingError),
|
||||||
/// The json-document contains a field that is not declared in the schema.
|
/// The json-document contains a field that is not declared in the schema.
|
||||||
#[fail(display = "The json-document contains an unknown field: {:?}", _0)]
|
#[fail(
|
||||||
|
display = "The document contains a field that is not declared in the schema: {:?}",
|
||||||
|
_0
|
||||||
|
)]
|
||||||
NoSuchFieldInSchema(String),
|
NoSuchFieldInSchema(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +401,7 @@ mod tests {
|
|||||||
use crate::schema::*;
|
use crate::schema::*;
|
||||||
use matches::{assert_matches, matches};
|
use matches::{assert_matches, matches};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn is_indexed_test() {
|
pub fn is_indexed_test() {
|
||||||
@@ -495,6 +516,54 @@ mod tests {
|
|||||||
assert_eq!(doc, doc_serdeser);
|
assert_eq!(doc, doc_serdeser);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_document_from_nameddoc() {
|
||||||
|
let mut schema_builder = Schema::builder();
|
||||||
|
let title = schema_builder.add_text_field("title", TEXT);
|
||||||
|
let val = schema_builder.add_i64_field("val", INDEXED);
|
||||||
|
let schema = schema_builder.build();
|
||||||
|
let mut named_doc_map = BTreeMap::default();
|
||||||
|
named_doc_map.insert(
|
||||||
|
"title".to_string(),
|
||||||
|
vec![Value::from("title1"), Value::from("title2")],
|
||||||
|
);
|
||||||
|
named_doc_map.insert(
|
||||||
|
"val".to_string(),
|
||||||
|
vec![Value::from(14u64), Value::from(-1i64)],
|
||||||
|
);
|
||||||
|
let doc = schema
|
||||||
|
.convert_named_doc(NamedFieldDocument(named_doc_map))
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
doc.get_all(title),
|
||||||
|
vec![
|
||||||
|
&Value::from("title1".to_string()),
|
||||||
|
&Value::from("title2".to_string())
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
doc.get_all(val),
|
||||||
|
vec![&Value::from(14u64), &Value::from(-1i64)]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_document_from_nameddoc_error() {
|
||||||
|
let schema = Schema::builder().build();
|
||||||
|
let mut named_doc_map = BTreeMap::default();
|
||||||
|
named_doc_map.insert(
|
||||||
|
"title".to_string(),
|
||||||
|
vec![Value::from("title1"), Value::from("title2")],
|
||||||
|
);
|
||||||
|
let err = schema
|
||||||
|
.convert_named_doc(NamedFieldDocument(named_doc_map))
|
||||||
|
.unwrap_err();
|
||||||
|
assert_eq!(
|
||||||
|
err,
|
||||||
|
DocParsingError::NoSuchFieldInSchema("title".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_parse_document() {
|
pub fn test_parse_document() {
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ impl Term {
|
|||||||
/// Builds a term given a field, and a i64-value
|
/// Builds a term given a field, and a i64-value
|
||||||
///
|
///
|
||||||
/// Assuming the term has a field id of 1, and a i64 value of 3234,
|
/// Assuming the term has a field id of 1, and a i64 value of 3234,
|
||||||
/// the Term will have 8 bytes.
|
/// the Term will have 12 bytes.
|
||||||
///
|
///
|
||||||
/// The first four byte are dedicated to storing the field id as a u64.
|
/// The first four byte are dedicated to storing the field id as a u64.
|
||||||
/// The 4 following bytes are encoding the u64 value.
|
/// The 8 following bytes are encoding the u64 value.
|
||||||
pub fn from_field_i64(field: Field, val: i64) -> Term {
|
pub fn from_field_i64(field: Field, val: i64) -> Term {
|
||||||
let val_u64: u64 = common::i64_to_u64(val);
|
let val_u64: u64 = common::i64_to_u64(val);
|
||||||
Term::from_field_u64(field, val_u64)
|
Term::from_field_u64(field, val_u64)
|
||||||
@@ -33,11 +33,11 @@ impl Term {
|
|||||||
|
|
||||||
/// Builds a term given a field, and a f64-value
|
/// Builds a term given a field, and a f64-value
|
||||||
///
|
///
|
||||||
/// Assuming the term has a field id of 1, and a u64 value of 3234,
|
/// Assuming the term has a field id of 1, and a f64 value of 1.5,
|
||||||
/// the Term will have 8 bytes. <= this is wrong
|
/// the Term will have 12 bytes.
|
||||||
///
|
///
|
||||||
/// The first four byte are dedicated to storing the field id as a u64.
|
/// The first four byte are dedicated to storing the field id as a u64.
|
||||||
/// The 4 following bytes are encoding the u64 value.
|
/// The 8 following bytes are encoding the f64 as a u64 value.
|
||||||
pub fn from_field_f64(field: Field, val: f64) -> Term {
|
pub fn from_field_f64(field: Field, val: f64) -> Term {
|
||||||
let val_u64: u64 = common::f64_to_u64(val);
|
let val_u64: u64 = common::f64_to_u64(val);
|
||||||
Term::from_field_u64(field, val_u64)
|
Term::from_field_u64(field, val_u64)
|
||||||
@@ -46,10 +46,10 @@ impl Term {
|
|||||||
/// Builds a term given a field, and a DateTime value
|
/// Builds a term given a field, and a DateTime value
|
||||||
///
|
///
|
||||||
/// Assuming the term has a field id of 1, and a timestamp i64 value of 3234,
|
/// Assuming the term has a field id of 1, and a timestamp i64 value of 3234,
|
||||||
/// the Term will have 8 bytes.
|
/// the Term will have 12 bytes.
|
||||||
///
|
///
|
||||||
/// The first four byte are dedicated to storing the field id as a u64.
|
/// The first four byte are dedicated to storing the field id as a u64.
|
||||||
/// The 4 following bytes are encoding the DateTime as i64 timestamp value.
|
/// The 8 following bytes are encoding the DateTime as i64 timestamp value.
|
||||||
pub fn from_field_date(field: Field, val: &DateTime) -> Term {
|
pub fn from_field_date(field: Field, val: &DateTime) -> Term {
|
||||||
let val_timestamp = val.timestamp();
|
let val_timestamp = val.timestamp();
|
||||||
Term::from_field_i64(field, val_timestamp)
|
Term::from_field_i64(field, val_timestamp)
|
||||||
@@ -82,10 +82,10 @@ impl Term {
|
|||||||
/// Builds a term given a field, and a u64-value
|
/// Builds a term given a field, and a u64-value
|
||||||
///
|
///
|
||||||
/// Assuming the term has a field id of 1, and a u64 value of 3234,
|
/// Assuming the term has a field id of 1, and a u64 value of 3234,
|
||||||
/// the Term will have 8 bytes.
|
/// the Term will have 12 bytes.
|
||||||
///
|
///
|
||||||
/// The first four byte are dedicated to storing the field id as a u64.
|
/// The first four byte are dedicated to storing the field id as a u64.
|
||||||
/// The 4 following bytes are encoding the u64 value.
|
/// The 8 following bytes are encoding the u64 value.
|
||||||
pub fn from_field_u64(field: Field, val: u64) -> Term {
|
pub fn from_field_u64(field: Field, val: u64) -> Term {
|
||||||
let mut term = Term(vec![0u8; INT_TERM_LEN]);
|
let mut term = Term(vec![0u8; INT_TERM_LEN]);
|
||||||
term.set_field(field);
|
term.set_field(field);
|
||||||
@@ -182,7 +182,7 @@ where
|
|||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
/// ... or returns an invalid value
|
/// ... or returns an invalid value
|
||||||
/// if the term is not a `i64` field.
|
/// if the term is not a `f64` field.
|
||||||
pub fn get_f64(&self) -> f64 {
|
pub fn get_f64(&self) -> f64 {
|
||||||
common::u64_to_f64(BigEndian::read_u64(&self.0.as_ref()[4..]))
|
common::u64_to_f64(BigEndian::read_u64(&self.0.as_ref()[4..]))
|
||||||
}
|
}
|
||||||
@@ -224,7 +224,12 @@ where
|
|||||||
|
|
||||||
impl fmt::Debug for Term {
|
impl fmt::Debug for Term {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "Term({:?})", &self.0[..])
|
write!(
|
||||||
|
f,
|
||||||
|
"Term(field={},bytes={:?})",
|
||||||
|
self.field().0,
|
||||||
|
self.value_bytes()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl FragmentCandidate {
|
|||||||
fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
|
fn try_add_token(&mut self, token: &Token, terms: &BTreeMap<String, f32>) {
|
||||||
self.stop_offset = token.offset_to;
|
self.stop_offset = token.offset_to;
|
||||||
|
|
||||||
if let Some(score) = terms.get(&token.text.to_lowercase()) {
|
if let Some(&score) = terms.get(&token.text.to_lowercase()) {
|
||||||
self.score += score;
|
self.score += score;
|
||||||
self.highlighted
|
self.highlighted
|
||||||
.push(HighlightSection::new(token.offset_from, token.offset_to));
|
.push(HighlightSection::new(token.offset_from, token.offset_to));
|
||||||
@@ -142,7 +142,7 @@ impl Snippet {
|
|||||||
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
|
/// Fragments must be valid in the sense that `&text[fragment.start..fragment.stop]`\
|
||||||
/// has to be a valid string.
|
/// has to be a valid string.
|
||||||
fn search_fragments<'a>(
|
fn search_fragments<'a>(
|
||||||
tokenizer: &dyn BoxedTokenizer,
|
tokenizer: &BoxedTokenizer,
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
terms: &BTreeMap<String, f32>,
|
terms: &BTreeMap<String, f32>,
|
||||||
max_num_chars: usize,
|
max_num_chars: usize,
|
||||||
@@ -150,7 +150,6 @@ fn search_fragments<'a>(
|
|||||||
let mut token_stream = tokenizer.token_stream(text);
|
let mut token_stream = tokenizer.token_stream(text);
|
||||||
let mut fragment = FragmentCandidate::new(0);
|
let mut fragment = FragmentCandidate::new(0);
|
||||||
let mut fragments: Vec<FragmentCandidate> = vec![];
|
let mut fragments: Vec<FragmentCandidate> = vec![];
|
||||||
|
|
||||||
while let Some(next) = token_stream.next() {
|
while let Some(next) = token_stream.next() {
|
||||||
if (next.offset_to - fragment.start_offset) > max_num_chars {
|
if (next.offset_to - fragment.start_offset) > max_num_chars {
|
||||||
if fragment.score > 0.0 {
|
if fragment.score > 0.0 {
|
||||||
@@ -214,11 +213,9 @@ fn select_best_fragment_combination(fragments: &[FragmentCandidate], text: &str)
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # #[macro_use]
|
|
||||||
/// # extern crate tantivy;
|
|
||||||
/// # use tantivy::Index;
|
|
||||||
/// # use tantivy::schema::{Schema, TEXT};
|
|
||||||
/// # use tantivy::query::QueryParser;
|
/// # use tantivy::query::QueryParser;
|
||||||
|
/// # use tantivy::schema::{Schema, TEXT};
|
||||||
|
/// # use tantivy::{doc, Index};
|
||||||
/// use tantivy::SnippetGenerator;
|
/// use tantivy::SnippetGenerator;
|
||||||
///
|
///
|
||||||
/// # fn main() -> tantivy::Result<()> {
|
/// # fn main() -> tantivy::Result<()> {
|
||||||
@@ -254,7 +251,7 @@ fn select_best_fragment_combination(fragments: &[FragmentCandidate], text: &str)
|
|||||||
/// ```
|
/// ```
|
||||||
pub struct SnippetGenerator {
|
pub struct SnippetGenerator {
|
||||||
terms_text: BTreeMap<String, f32>,
|
terms_text: BTreeMap<String, f32>,
|
||||||
tokenizer: Box<dyn BoxedTokenizer>,
|
tokenizer: BoxedTokenizer,
|
||||||
field: Field,
|
field: Field,
|
||||||
max_num_chars: usize,
|
max_num_chars: usize,
|
||||||
}
|
}
|
||||||
@@ -316,12 +313,8 @@ impl SnippetGenerator {
|
|||||||
|
|
||||||
/// Generates a snippet for the given text.
|
/// Generates a snippet for the given text.
|
||||||
pub fn snippet(&self, text: &str) -> Snippet {
|
pub fn snippet(&self, text: &str) -> Snippet {
|
||||||
let fragment_candidates = search_fragments(
|
let fragment_candidates =
|
||||||
&*self.tokenizer,
|
search_fragments(&self.tokenizer, &text, &self.terms_text, self.max_num_chars);
|
||||||
&text,
|
|
||||||
&self.terms_text,
|
|
||||||
self.max_num_chars,
|
|
||||||
);
|
|
||||||
select_best_fragment_combination(&fragment_candidates[..], &text)
|
select_best_fragment_combination(&fragment_candidates[..], &text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -331,7 +324,7 @@ mod tests {
|
|||||||
use super::{search_fragments, select_best_fragment_combination};
|
use super::{search_fragments, select_best_fragment_combination};
|
||||||
use crate::query::QueryParser;
|
use crate::query::QueryParser;
|
||||||
use crate::schema::{IndexRecordOption, Schema, TextFieldIndexing, TextOptions, TEXT};
|
use crate::schema::{IndexRecordOption, Schema, TextFieldIndexing, TextOptions, TEXT};
|
||||||
use crate::tokenizer::{box_tokenizer, SimpleTokenizer};
|
use crate::tokenizer::SimpleTokenizer;
|
||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::SnippetGenerator;
|
use crate::SnippetGenerator;
|
||||||
use maplit::btreemap;
|
use maplit::btreemap;
|
||||||
@@ -355,12 +348,12 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet() {
|
fn test_snippet() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
let terms = btreemap! {
|
let terms = btreemap! {
|
||||||
String::from("rust") => 1.0,
|
String::from("rust") => 1.0,
|
||||||
String::from("language") => 0.9
|
String::from("language") => 0.9
|
||||||
};
|
};
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, TEST_TEXT, &terms, 100);
|
let fragments = search_fragments(&boxed_tokenizer, TEST_TEXT, &terms, 100);
|
||||||
assert_eq!(fragments.len(), 7);
|
assert_eq!(fragments.len(), 7);
|
||||||
{
|
{
|
||||||
let first = &fragments[0];
|
let first = &fragments[0];
|
||||||
@@ -382,13 +375,13 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_scored_fragment() {
|
fn test_snippet_scored_fragment() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
{
|
{
|
||||||
let terms = btreemap! {
|
let terms = btreemap! {
|
||||||
String::from("rust") =>1.0f32,
|
String::from("rust") =>1.0f32,
|
||||||
String::from("language") => 0.9f32
|
String::from("language") => 0.9f32
|
||||||
};
|
};
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, TEST_TEXT, &terms, 20);
|
let fragments = search_fragments(&boxed_tokenizer, TEST_TEXT, &terms, 20);
|
||||||
{
|
{
|
||||||
let first = &fragments[0];
|
let first = &fragments[0];
|
||||||
assert_eq!(first.score, 1.0);
|
assert_eq!(first.score, 1.0);
|
||||||
@@ -397,13 +390,13 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
let snippet = select_best_fragment_combination(&fragments[..], &TEST_TEXT);
|
let snippet = select_best_fragment_combination(&fragments[..], &TEST_TEXT);
|
||||||
assert_eq!(snippet.to_html(), "<b>Rust</b> is a systems")
|
assert_eq!(snippet.to_html(), "<b>Rust</b> is a systems")
|
||||||
}
|
}
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
{
|
{
|
||||||
let terms = btreemap! {
|
let terms = btreemap! {
|
||||||
String::from("rust") =>0.9f32,
|
String::from("rust") =>0.9f32,
|
||||||
String::from("language") => 1.0f32
|
String::from("language") => 1.0f32
|
||||||
};
|
};
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, TEST_TEXT, &terms, 20);
|
let fragments = search_fragments(&boxed_tokenizer, TEST_TEXT, &terms, 20);
|
||||||
//assert_eq!(fragments.len(), 7);
|
//assert_eq!(fragments.len(), 7);
|
||||||
{
|
{
|
||||||
let first = &fragments[0];
|
let first = &fragments[0];
|
||||||
@@ -417,14 +410,14 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_in_second_fragment() {
|
fn test_snippet_in_second_fragment() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
|
|
||||||
let text = "a b c d e f g";
|
let text = "a b c d e f g";
|
||||||
|
|
||||||
let mut terms = BTreeMap::new();
|
let mut terms = BTreeMap::new();
|
||||||
terms.insert(String::from("c"), 1.0);
|
terms.insert(String::from("c"), 1.0);
|
||||||
|
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, &text, &terms, 3);
|
let fragments = search_fragments(&boxed_tokenizer, &text, &terms, 3);
|
||||||
|
|
||||||
assert_eq!(fragments.len(), 1);
|
assert_eq!(fragments.len(), 1);
|
||||||
{
|
{
|
||||||
@@ -441,14 +434,14 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_with_term_at_the_end_of_fragment() {
|
fn test_snippet_with_term_at_the_end_of_fragment() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
|
|
||||||
let text = "a b c d e f f g";
|
let text = "a b c d e f f g";
|
||||||
|
|
||||||
let mut terms = BTreeMap::new();
|
let mut terms = BTreeMap::new();
|
||||||
terms.insert(String::from("f"), 1.0);
|
terms.insert(String::from("f"), 1.0);
|
||||||
|
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, &text, &terms, 3);
|
let fragments = search_fragments(&boxed_tokenizer, &text, &terms, 3);
|
||||||
|
|
||||||
assert_eq!(fragments.len(), 2);
|
assert_eq!(fragments.len(), 2);
|
||||||
{
|
{
|
||||||
@@ -465,7 +458,7 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_with_second_fragment_has_the_highest_score() {
|
fn test_snippet_with_second_fragment_has_the_highest_score() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
|
|
||||||
let text = "a b c d e f g";
|
let text = "a b c d e f g";
|
||||||
|
|
||||||
@@ -473,7 +466,7 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
terms.insert(String::from("f"), 1.0);
|
terms.insert(String::from("f"), 1.0);
|
||||||
terms.insert(String::from("a"), 0.9);
|
terms.insert(String::from("a"), 0.9);
|
||||||
|
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, &text, &terms, 7);
|
let fragments = search_fragments(&boxed_tokenizer, &text, &terms, 7);
|
||||||
|
|
||||||
assert_eq!(fragments.len(), 2);
|
assert_eq!(fragments.len(), 2);
|
||||||
{
|
{
|
||||||
@@ -490,14 +483,14 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_with_term_not_in_text() {
|
fn test_snippet_with_term_not_in_text() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
|
|
||||||
let text = "a b c d";
|
let text = "a b c d";
|
||||||
|
|
||||||
let mut terms = BTreeMap::new();
|
let mut terms = BTreeMap::new();
|
||||||
terms.insert(String::from("z"), 1.0);
|
terms.insert(String::from("z"), 1.0);
|
||||||
|
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, &text, &terms, 3);
|
let fragments = search_fragments(&boxed_tokenizer, &text, &terms, 3);
|
||||||
|
|
||||||
assert_eq!(fragments.len(), 0);
|
assert_eq!(fragments.len(), 0);
|
||||||
|
|
||||||
@@ -508,12 +501,12 @@ Survey in 2016, 2017, and 2018."#;
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_snippet_with_no_terms() {
|
fn test_snippet_with_no_terms() {
|
||||||
let boxed_tokenizer = box_tokenizer(SimpleTokenizer);
|
let boxed_tokenizer = SimpleTokenizer.into();
|
||||||
|
|
||||||
let text = "a b c d";
|
let text = "a b c d";
|
||||||
|
|
||||||
let terms = BTreeMap::new();
|
let terms = BTreeMap::new();
|
||||||
let fragments = search_fragments(&*boxed_tokenizer, &text, &terms, 3);
|
let fragments = search_fragments(&boxed_tokenizer, &text, &terms, 3);
|
||||||
assert_eq!(fragments.len(), 0);
|
assert_eq!(fragments.len(), 0);
|
||||||
|
|
||||||
let snippet = select_best_fragment_combination(&fragments[..], &text);
|
let snippet = select_best_fragment_combination(&fragments[..], &text);
|
||||||
|
|||||||
@@ -120,17 +120,16 @@ pub mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(all(test, feature = "unstable"))]
|
#[cfg(all(test, feature = "unstable"))]
|
||||||
mod bench {
|
mod bench {
|
||||||
|
|
||||||
use super::tests::write_lorem_ipsum_store;
|
use super::tests::write_lorem_ipsum_store;
|
||||||
use directory::Directory;
|
use crate::directory::Directory;
|
||||||
use directory::RAMDirectory;
|
use crate::directory::RAMDirectory;
|
||||||
|
use crate::store::StoreReader;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use store::StoreReader;
|
|
||||||
use test::Bencher;
|
use test::Bencher;
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
|
|||||||
@@ -165,5 +165,4 @@ mod tests {
|
|||||||
assert_eq!(output.len(), 65);
|
assert_eq!(output.len(), 65);
|
||||||
assert_eq!(output[0], 128u8 + 3u8);
|
assert_eq!(output[0], 128u8 + 3u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use super::skiplist::SkipListBuilder;
|
|||||||
use super::StoreReader;
|
use super::StoreReader;
|
||||||
use crate::common::CountingWriter;
|
use crate::common::CountingWriter;
|
||||||
use crate::common::{BinarySerializable, VInt};
|
use crate::common::{BinarySerializable, VInt};
|
||||||
|
use crate::directory::TerminatingWrite;
|
||||||
use crate::directory::WritePtr;
|
use crate::directory::WritePtr;
|
||||||
use crate::schema::Document;
|
use crate::schema::Document;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
@@ -109,6 +110,6 @@ impl StoreWriter {
|
|||||||
self.offset_index_writer.write(&mut self.writer)?;
|
self.offset_index_writer.write(&mut self.writer)?;
|
||||||
header_offset.serialize(&mut self.writer)?;
|
header_offset.serialize(&mut self.writer)?;
|
||||||
self.doc.serialize(&mut self.writer)?;
|
self.doc.serialize(&mut self.writer)?;
|
||||||
self.writer.flush()
|
self.writer.terminate()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -328,5 +328,4 @@ mod tests {
|
|||||||
assert_eq!(term_info_store.get(i as u64), term_infos[i]);
|
assert_eq!(term_info_store.get(i as u64), term_infos[i]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```
|
//! ```rust
|
||||||
//! extern crate tantivy;
|
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy::tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
|
|||||||
@@ -98,10 +98,6 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_lowercaser() {
|
fn test_lowercaser() {
|
||||||
assert_eq!(lowercase_helper("Tree"), vec!["tree".to_string()]);
|
assert_eq!(lowercase_helper("Tree"), vec!["tree".to_string()]);
|
||||||
assert_eq!(
|
assert_eq!(lowercase_helper("Русский"), vec!["русский".to_string()]);
|
||||||
lowercase_helper("Русский"),
|
|
||||||
vec!["русский".to_string()]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
//! You must define in your schema which tokenizer should be used for
|
//! You must define in your schema which tokenizer should be used for
|
||||||
//! each of your fields :
|
//! each of your fields :
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```rust
|
||||||
//! extern crate tantivy;
|
|
||||||
//! use tantivy::schema::*;
|
//! use tantivy::schema::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
@@ -65,8 +64,6 @@
|
|||||||
//! For instance, the `en_stem` is defined as follows.
|
//! For instance, the `en_stem` is defined as follows.
|
||||||
//!
|
//!
|
||||||
//! ```rust
|
//! ```rust
|
||||||
//! # extern crate tantivy;
|
|
||||||
//!
|
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy::tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
@@ -80,8 +77,7 @@
|
|||||||
//! Once your tokenizer is defined, you need to
|
//! Once your tokenizer is defined, you need to
|
||||||
//! 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
|
||||||
//! # extern crate tantivy;
|
|
||||||
//! # use tantivy::schema::Schema;
|
//! # use tantivy::schema::Schema;
|
||||||
//! # use tantivy::tokenizer::*;
|
//! # use tantivy::tokenizer::*;
|
||||||
//! # use tantivy::Index;
|
//! # use tantivy::Index;
|
||||||
@@ -101,8 +97,7 @@
|
|||||||
//!
|
//!
|
||||||
//! # Example
|
//! # Example
|
||||||
//!
|
//!
|
||||||
//! ```
|
//! ```rust
|
||||||
//! extern crate tantivy;
|
|
||||||
//! 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;
|
||||||
@@ -155,7 +150,6 @@ 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(crate) use self::tokenizer::box_tokenizer;
|
|
||||||
pub use self::tokenizer::BoxedTokenizer;
|
pub use self::tokenizer::BoxedTokenizer;
|
||||||
|
|
||||||
pub use self::tokenizer::{Token, TokenFilter, TokenStream, Tokenizer};
|
pub use self::tokenizer::{Token, TokenFilter, TokenStream, Tokenizer};
|
||||||
@@ -287,5 +281,4 @@ pub mod tests {
|
|||||||
assert!(tokens.is_empty());
|
assert!(tokens.is_empty());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,8 +29,7 @@ use super::{Token, TokenStream, Tokenizer};
|
|||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```rust
|
||||||
/// # extern crate tantivy;
|
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy::tokenizer::*;
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
/// let tokenizer = NgramTokenizer::new(2, 3, false);
|
/// let tokenizer = NgramTokenizer::new(2, 3, false);
|
||||||
@@ -461,5 +460,4 @@ mod tests {
|
|||||||
assert_eq!(it.next(), Some((8, 9)));
|
assert_eq!(it.next(), Some((8, 9)));
|
||||||
assert_eq!(it.next(), None);
|
assert_eq!(it.next(), None);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```
|
//! ```rust
|
||||||
//! extern crate tantivy;
|
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy::tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
//! # Example
|
//! # Example
|
||||||
//! ```
|
//! ```rust
|
||||||
//! extern crate tantivy;
|
|
||||||
//! use tantivy::tokenizer::*;
|
//! use tantivy::tokenizer::*;
|
||||||
//!
|
//!
|
||||||
//! # fn main() {
|
//! # fn main() {
|
||||||
|
|||||||
@@ -97,5 +97,4 @@ mod tests {
|
|||||||
|
|
||||||
assert!(!token_chain.advance());
|
assert!(!token_chain.advance());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -56,8 +56,6 @@ pub trait Tokenizer<'a>: Sized + Clone {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # extern crate tantivy;
|
|
||||||
///
|
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy::tokenizer::*;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
@@ -80,7 +78,7 @@ pub trait Tokenizer<'a>: Sized + Clone {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A boxed tokenizer
|
/// A boxed tokenizer
|
||||||
pub trait BoxedTokenizer: Send + Sync {
|
trait BoxedTokenizerTrait: Send + Sync {
|
||||||
/// Tokenize a `&str`
|
/// Tokenize a `&str`
|
||||||
fn token_stream<'a>(&self, text: &'a str) -> Box<dyn TokenStream + 'a>;
|
fn token_stream<'a>(&self, text: &'a str) -> Box<dyn TokenStream + 'a>;
|
||||||
|
|
||||||
@@ -92,7 +90,41 @@ pub trait BoxedTokenizer: Send + Sync {
|
|||||||
fn token_stream_texts<'b>(&self, texts: &'b [&'b str]) -> Box<dyn TokenStream + 'b>;
|
fn token_stream_texts<'b>(&self, texts: &'b [&'b str]) -> Box<dyn TokenStream + 'b>;
|
||||||
|
|
||||||
/// Return a boxed clone of the tokenizer
|
/// Return a boxed clone of the tokenizer
|
||||||
fn boxed_clone(&self) -> Box<dyn BoxedTokenizer>;
|
fn boxed_clone(&self) -> BoxedTokenizer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A boxed tokenizer
|
||||||
|
pub struct BoxedTokenizer(Box<dyn BoxedTokenizerTrait>);
|
||||||
|
|
||||||
|
impl<T> From<T> for BoxedTokenizer
|
||||||
|
where
|
||||||
|
T: 'static + Send + Sync + for<'a> Tokenizer<'a>,
|
||||||
|
{
|
||||||
|
fn from(tokenizer: T) -> BoxedTokenizer {
|
||||||
|
BoxedTokenizer(Box::new(BoxableTokenizer(tokenizer)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl BoxedTokenizer {
|
||||||
|
/// Tokenize a `&str`
|
||||||
|
pub fn token_stream<'a>(&self, text: &'a str) -> Box<dyn TokenStream + 'a> {
|
||||||
|
self.0.token_stream(text)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Tokenize an array`&str`
|
||||||
|
///
|
||||||
|
/// The resulting `TokenStream` is equivalent to what would be obtained if the &str were
|
||||||
|
/// one concatenated `&str`, with an artificial position gap of `2` between the different fields
|
||||||
|
/// to prevent accidental `PhraseQuery` to match accross two terms.
|
||||||
|
pub fn token_stream_texts<'b>(&self, texts: &'b [&'b str]) -> Box<dyn TokenStream + 'b> {
|
||||||
|
self.0.token_stream_texts(texts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for BoxedTokenizer {
|
||||||
|
fn clone(&self) -> BoxedTokenizer {
|
||||||
|
self.0.boxed_clone()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -100,7 +132,7 @@ struct BoxableTokenizer<A>(A)
|
|||||||
where
|
where
|
||||||
A: for<'a> Tokenizer<'a> + Send + Sync;
|
A: for<'a> Tokenizer<'a> + Send + Sync;
|
||||||
|
|
||||||
impl<A> BoxedTokenizer for BoxableTokenizer<A>
|
impl<A> BoxedTokenizerTrait for BoxableTokenizer<A>
|
||||||
where
|
where
|
||||||
A: 'static + Send + Sync + for<'a> Tokenizer<'a>,
|
A: 'static + Send + Sync + for<'a> Tokenizer<'a>,
|
||||||
{
|
{
|
||||||
@@ -125,18 +157,11 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn boxed_clone(&self) -> Box<dyn BoxedTokenizer> {
|
fn boxed_clone(&self) -> BoxedTokenizer {
|
||||||
Box::new(self.clone())
|
self.0.clone().into()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn box_tokenizer<A>(a: A) -> Box<dyn BoxedTokenizer>
|
|
||||||
where
|
|
||||||
A: 'static + Send + Sync + for<'a> Tokenizer<'a>,
|
|
||||||
{
|
|
||||||
Box::new(BoxableTokenizer(a))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> TokenStream for Box<dyn TokenStream + 'b> {
|
impl<'b> TokenStream for Box<dyn TokenStream + 'b> {
|
||||||
fn advance(&mut self) -> bool {
|
fn advance(&mut self) -> bool {
|
||||||
let token_stream: &mut dyn TokenStream = self.borrow_mut();
|
let token_stream: &mut dyn TokenStream = self.borrow_mut();
|
||||||
@@ -161,7 +186,6 @@ impl<'b> TokenStream for Box<dyn TokenStream + 'b> {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// extern crate tantivy;
|
|
||||||
/// use tantivy::tokenizer::*;
|
/// use tantivy::tokenizer::*;
|
||||||
///
|
///
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
@@ -203,7 +227,6 @@ pub trait TokenStream {
|
|||||||
/// and `.token()`.
|
/// and `.token()`.
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # extern crate tantivy;
|
|
||||||
/// # use tantivy::tokenizer::*;
|
/// # use tantivy::tokenizer::*;
|
||||||
/// #
|
/// #
|
||||||
/// # fn main() {
|
/// # fn main() {
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user