mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-05 16:52:55 +00:00
Compare commits
2 Commits
revert-122
...
wasm-frien
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
89f91b1b58 | ||
|
|
19965c46bc |
7
.github/dependabot.yml
vendored
7
.github/dependabot.yml
vendored
@@ -6,10 +6,3 @@ updates:
|
||||
interval: daily
|
||||
time: "20:00"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
- package-ecosystem: "github-actions"
|
||||
directory: "/"
|
||||
schedule:
|
||||
interval: daily
|
||||
time: "20:00"
|
||||
open-pull-requests-limit: 10
|
||||
|
||||
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --all-features --workspace --lcov --output-path lcov.info
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v2
|
||||
uses: codecov/codecov-action@v1
|
||||
with:
|
||||
token: ${{ secrets.CODECOV_TOKEN }} # not required for public repos
|
||||
files: lcov.info
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,4 @@
|
||||
tantivy.iml
|
||||
.cargo
|
||||
proptest-regressions
|
||||
*.swp
|
||||
target
|
||||
|
||||
92
.travis.yml
Normal file
92
.travis.yml
Normal file
@@ -0,0 +1,92 @@
|
||||
# Based on the "trust" template v0.1.2
|
||||
# https://github.com/japaric/trust/tree/v0.1.2
|
||||
|
||||
dist: trusty
|
||||
language: rust
|
||||
services: docker
|
||||
sudo: required
|
||||
|
||||
env:
|
||||
global:
|
||||
- CRATE_NAME=tantivy
|
||||
- TRAVIS_CARGO_NIGHTLY_FEATURE=""
|
||||
# - secure: eC8HjTi1wgRVCsMAeXEXt8Ckr0YBSGOEnQkkW4/Nde/OZ9jJjz2nmP1ELQlDE7+czHub2QvYtDMG0parcHZDx/Kus0yvyn08y3g2rhGIiE7y8OCvQm1Mybu2D/p7enm6shXquQ6Z5KRfRq+18mHy80wy9ABMA/ukEZdvnfQ76/Een8/Lb0eHaDoXDXn3PqLVtByvSfQQ7OhS60dEScu8PWZ6/l1057P5NpdWbMExBE7Ro4zYXNhkJeGZx0nP/Bd4Jjdt1XfPzMEybV6NZ5xsTILUBFTmOOt603IsqKGov089NExqxYu5bD3K+S4MzF1Nd6VhomNPJqLDCfhlymJCUj5n5Ku4yidlhQbM4Ej9nGrBalJnhcjBjPua5tmMF2WCxP9muKn/2tIOu1/+wc0vMf9Yd3wKIkf5+FtUxCgs2O+NslWvmOMAMI/yD25m7hb4t1IwE/4Bk+GVcWJRWXbo0/m6ZUHzRzdjUY2a1qvw7C9udzdhg7gcnXwsKrSWi2NjMiIVw86l+Zim0nLpKIN41sxZHLaFRG63Ki8zQ/481LGn32awJ6i3sizKS0WD+N1DfR2qYMrwYHaMN0uR0OFXYTJkFvTFttAeUY3EKmRKAuMhmO2YRdSr4/j/G5E9HMc1gSGJj6PxgpQU7EpvxRsmoVAEJr0mszmOj9icGHep/FM=
|
||||
|
||||
addons:
|
||||
apt:
|
||||
sources:
|
||||
- ubuntu-toolchain-r-test
|
||||
- kalakris-cmake
|
||||
packages:
|
||||
- gcc-4.8
|
||||
- g++-4.8
|
||||
- libcurl4-openssl-dev
|
||||
- libelf-dev
|
||||
- libdw-dev
|
||||
- binutils-dev
|
||||
- cmake
|
||||
|
||||
matrix:
|
||||
include:
|
||||
# Android
|
||||
- env: TARGET=aarch64-linux-android DISABLE_TESTS=1
|
||||
#- env: TARGET=arm-linux-androideabi DISABLE_TESTS=1
|
||||
#- env: TARGET=armv7-linux-androideabi DISABLE_TESTS=1
|
||||
#- env: TARGET=i686-linux-android DISABLE_TESTS=1
|
||||
#- env: TARGET=x86_64-linux-android DISABLE_TESTS=1
|
||||
|
||||
# Linux
|
||||
#- env: TARGET=aarch64-unknown-linux-gnu
|
||||
#- env: TARGET=i686-unknown-linux-gnu
|
||||
- env: TARGET=x86_64-unknown-linux-gnu CODECOV=1 #UPLOAD_DOCS=1
|
||||
# - env: TARGET=x86_64-unknown-linux-musl CODECOV=1
|
||||
# OSX
|
||||
#- env: TARGET=x86_64-apple-darwin
|
||||
# os: osx
|
||||
|
||||
before_install:
|
||||
- set -e
|
||||
- rustup self update
|
||||
- rustup component add rustfmt
|
||||
|
||||
install:
|
||||
- sh ci/install.sh
|
||||
- source ~/.cargo/env || true
|
||||
- env | grep "TRAVIS"
|
||||
|
||||
before_script:
|
||||
- export PATH=$HOME/.cargo/bin:$PATH
|
||||
- cargo install cargo-update || echo "cargo-update already installed"
|
||||
- cargo install cargo-travis || echo "cargo-travis already installed"
|
||||
|
||||
script:
|
||||
- bash ci/script.sh
|
||||
- cargo fmt --all -- --check
|
||||
|
||||
before_deploy:
|
||||
- sh ci/before_deploy.sh
|
||||
|
||||
after_success:
|
||||
# Needs GH_TOKEN env var to be set in travis settings
|
||||
- if [[ -v GH_TOKEN ]]; then echo "GH TOKEN IS SET"; else echo "GH TOKEN NOT SET"; fi
|
||||
- if [[ -v UPLOAD_DOCS ]]; then cargo doc; cargo doc-upload; else echo "doc upload disabled."; fi
|
||||
|
||||
#cache: cargo
|
||||
#before_cache:
|
||||
# # Travis can't cache files that are not readable by "others"
|
||||
# - chmod -R a+r $HOME/.cargo
|
||||
# - find ./target/debug -type f -maxdepth 1 -delete
|
||||
# - rm -f ./target/.rustc_info.json
|
||||
# - rm -fr ./target/debug/{deps,.fingerprint}/tantivy*
|
||||
# - rm -r target/debug/examples/
|
||||
# - ls -1 examples/ | sed -e 's/\.rs$//' | xargs -I "{}" find target/* -name "*{}*" -type f -delete
|
||||
|
||||
#branches:
|
||||
# only:
|
||||
# # release tags
|
||||
# - /^v\d+\.\d+\.\d+.*$/
|
||||
# - master
|
||||
|
||||
notifications:
|
||||
email:
|
||||
on_success: never
|
||||
11
CHANGELOG.md
11
CHANGELOG.md
@@ -1,15 +1,6 @@
|
||||
Tantivy 0.17
|
||||
================================
|
||||
- Change to non-strict schema. Ignore fields in data which are not defined in schema. Previously this returned an error. #1211
|
||||
|
||||
Tantivy 0.16.2
|
||||
================================
|
||||
- Bugfix in FuzzyTermQuery. (tranposition_cost_one was not doing anything)
|
||||
|
||||
Tantivy 0.16.1
|
||||
========================
|
||||
- Major Bugfix on multivalued fastfield. #1151
|
||||
- Demux operation (@PSeitz)
|
||||
|
||||
Tantivy 0.16.0
|
||||
=========================
|
||||
@@ -122,7 +113,7 @@ Tantivy 0.12.0
|
||||
## How to update?
|
||||
|
||||
Crates relying on custom tokenizer, or registering tokenizer in the manager will require some
|
||||
minor changes. Check https://github.com/quickwit-inc/tantivy/blob/main/examples/custom_tokenizer.rs
|
||||
minor changes. Check https://github.com/tantivy-search/tantivy/blob/main/examples/custom_tokenizer.rs
|
||||
to check for some code sample.
|
||||
|
||||
Tantivy 0.11.3
|
||||
|
||||
20
Cargo.toml
20
Cargo.toml
@@ -1,13 +1,13 @@
|
||||
[package]
|
||||
name = "tantivy"
|
||||
version = "0.17.0-dev"
|
||||
version = "0.16.1"
|
||||
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||
license = "MIT"
|
||||
categories = ["database-implementations", "data-structures"]
|
||||
description = """Search engine library"""
|
||||
documentation = "https://docs.rs/tantivy/"
|
||||
homepage = "https://github.com/quickwit-inc/tantivy"
|
||||
repository = "https://github.com/quickwit-inc/tantivy"
|
||||
homepage = "https://github.com/tantivy-search/tantivy"
|
||||
repository = "https://github.com/tantivy-search/tantivy"
|
||||
readme = "README.md"
|
||||
keywords = ["search", "information", "retrieval"]
|
||||
edition = "2018"
|
||||
@@ -20,12 +20,13 @@ once_cell = "1.7.2"
|
||||
regex ={ version = "1.5.4", default-features = false, features = ["std"] }
|
||||
tantivy-fst = "0.3"
|
||||
memmap2 = {version = "0.5", optional=true}
|
||||
lz4_flex = { version = "0.9", default-features = false, features = ["checked-decode"], optional = true }
|
||||
lz4_flex = { version = "0.9.0", default-features = false, features = ["checked-decode"], optional = true }
|
||||
brotli = { version = "3.3", optional = true }
|
||||
snap = { version = "1.0.5", optional = true }
|
||||
tempfile = { version = "3.2", optional = true }
|
||||
log = "0.4.14"
|
||||
serde = { version = "1.0.126", features = ["derive"] }
|
||||
serde_closure = "0.3"
|
||||
serde_json = "1.0.64"
|
||||
num_cpus = "1.13"
|
||||
fs2={ version = "0.4.3", optional = true }
|
||||
@@ -37,7 +38,7 @@ tantivy-query-grammar = { version="0.15.0", path="./query-grammar" }
|
||||
tantivy-bitpacker = { version="0.1", path="./bitpacker" }
|
||||
common = { version = "0.1", path = "./common/", package = "tantivy-common" }
|
||||
fastfield_codecs = { version="0.1", path="./fastfield_codecs", default-features = false }
|
||||
ownedbytes = { version="0.2", path="./ownedbytes" }
|
||||
ownedbytes = { version="0.1", path="./ownedbytes" }
|
||||
stable_deref_trait = "1.2"
|
||||
rust-stemmers = "1.2"
|
||||
downcast-rs = "1.2"
|
||||
@@ -46,15 +47,16 @@ census = "0.4"
|
||||
fnv = "1.0.7"
|
||||
thiserror = "1.0.24"
|
||||
htmlescape = "0.3.1"
|
||||
fail = "0.5"
|
||||
fail = "0.4"
|
||||
murmurhash32 = "0.2"
|
||||
chrono = "0.4.19"
|
||||
smallvec = "1.6.1"
|
||||
rayon = "1.5"
|
||||
lru = "0.7.0"
|
||||
fastdivide = "0.3"
|
||||
itertools = "0.10.0"
|
||||
measure_time = "0.8.0"
|
||||
measure_time = "0.7.0"
|
||||
wasm-mt = "0.1"
|
||||
wasm-mt-pool = "0.1"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
winapi = "0.3.9"
|
||||
@@ -69,7 +71,7 @@ test-env-log = "0.2.7"
|
||||
env_logger = "0.9.0"
|
||||
|
||||
[dev-dependencies.fail]
|
||||
version = "0.5"
|
||||
version = "0.4"
|
||||
features = ["failpoints"]
|
||||
|
||||
[profile.release]
|
||||
|
||||
16
README.md
16
README.md
@@ -1,8 +1,8 @@
|
||||
|
||||
[](https://docs.rs/crate/tantivy/)
|
||||
[](https://github.com/quickwit-inc/tantivy/actions/workflows/test.yml)
|
||||
[](https://codecov.io/gh/quickwit-inc/tantivy)
|
||||
[](https://discord.gg/MT27AG5EVE)
|
||||
[](https://github.com/tantivy-search/tantivy/actions/workflows/test.yml)
|
||||
[](https://codecov.io/gh/tantivy-search/tantivy)
|
||||
[](https://gitter.im/tantivy-search/tantivy?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](https://opensource.org/licenses/MIT)
|
||||
[](https://crates.io/crates/tantivy)
|
||||
|
||||
@@ -17,6 +17,9 @@
|
||||
[](https://sourcerer.io/fame/fulmicoton/tantivy-search/tantivy/links/6)
|
||||
[](https://sourcerer.io/fame/fulmicoton/tantivy-search/tantivy/links/7)
|
||||
|
||||
[](https://www.patreon.com/fulmicoton)
|
||||
|
||||
|
||||
**Tantivy** is a **full text search engine library** written in Rust.
|
||||
|
||||
It is closer to [Apache Lucene](https://lucene.apache.org/) than to [Elasticsearch](https://www.elastic.co/products/elasticsearch) or [Apache Solr](https://lucene.apache.org/solr/) in the sense it is not
|
||||
@@ -75,12 +78,13 @@ It walks you through getting a wikipedia search engine up and running in a few m
|
||||
|
||||
There are many ways to support this project.
|
||||
|
||||
- Use Tantivy and tell us about your experience on [Discord](https://discord.gg/MT27AG5EVE) or by email (paul.masurel@gmail.com)
|
||||
- Use Tantivy and tell us about your experience on [Gitter](https://gitter.im/tantivy-search/tantivy) or by email (paul.masurel@gmail.com)
|
||||
- Report bugs
|
||||
- Write a blog post
|
||||
- Help with documentation by asking questions or submitting PRs
|
||||
- Contribute code (you can join [our Discord server](https://discord.gg/MT27AG5EVE))
|
||||
- Contribute code (you can join [our Gitter](https://gitter.im/tantivy-search/tantivy))
|
||||
- Talk about Tantivy around you
|
||||
- [](https://www.patreon.com/fulmicoton)
|
||||
|
||||
# Contributing code
|
||||
|
||||
@@ -92,7 +96,7 @@ Tantivy compiles on stable Rust but requires `Rust >= 1.27`.
|
||||
To check out and run tests, you can simply run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/quickwit-inc/tantivy.git
|
||||
git clone https://github.com/tantivy-search/tantivy.git
|
||||
cd tantivy
|
||||
cargo build
|
||||
```
|
||||
|
||||
@@ -6,7 +6,7 @@ authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||
license = "MIT"
|
||||
categories = []
|
||||
description = """Tantivy-sub crate: bitpacking"""
|
||||
repository = "https://github.com/quickwit-inc/tantivy"
|
||||
repository = "https://github.com/tantivy-search/tantivy"
|
||||
keywords = []
|
||||
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ description = "common traits and utility functions used by multiple tantivy subc
|
||||
|
||||
[dependencies]
|
||||
byteorder = "1.4.3"
|
||||
ownedbytes = { version="0.2", path="../ownedbytes" }
|
||||
ownedbytes = { version="0.1", path="../ownedbytes" }
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1.0.0"
|
||||
|
||||
@@ -36,14 +36,10 @@ impl TinySet {
|
||||
writer.write_all(self.0.to_le_bytes().as_ref())
|
||||
}
|
||||
|
||||
pub fn into_bytes(self) -> [u8; 8] {
|
||||
self.0.to_le_bytes()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn deserialize(data: [u8; 8]) -> Self {
|
||||
pub fn deserialize(data: [u8; 8]) -> io::Result<Self> {
|
||||
let val: u64 = u64::from_le_bytes(data);
|
||||
TinySet(val)
|
||||
Ok(TinySet(val))
|
||||
}
|
||||
|
||||
/// Returns an empty `TinySet`.
|
||||
@@ -186,20 +182,42 @@ impl BitSet {
|
||||
///
|
||||
pub fn serialize<T: Write>(&self, writer: &mut T) -> io::Result<()> {
|
||||
writer.write_all(self.max_value.to_le_bytes().as_ref())?;
|
||||
for tinyset in self.tinysets.iter().cloned() {
|
||||
writer.write_all(&tinyset.into_bytes())?;
|
||||
|
||||
for tinyset in self.tinysets.iter() {
|
||||
tinyset.serialize(writer)?;
|
||||
}
|
||||
writer.flush()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deserialize a `BitSet`.
|
||||
///
|
||||
#[cfg(test)]
|
||||
pub fn deserialize(mut data: &[u8]) -> io::Result<Self> {
|
||||
let max_value: u32 = u32::from_le_bytes(data[..4].try_into().unwrap());
|
||||
data = &data[4..];
|
||||
|
||||
let mut len: u64 = 0;
|
||||
let mut tinysets = vec![];
|
||||
for chunk in data.chunks_exact(8) {
|
||||
let tinyset = TinySet::deserialize(chunk.try_into().unwrap())?;
|
||||
len += tinyset.len() as u64;
|
||||
tinysets.push(tinyset);
|
||||
}
|
||||
Ok(BitSet {
|
||||
tinysets: tinysets.into_boxed_slice(),
|
||||
len,
|
||||
max_value,
|
||||
})
|
||||
}
|
||||
|
||||
/// Create a new `BitSet` that may contain elements
|
||||
/// within `[0, max_val)`.
|
||||
pub fn with_max_value(max_value: u32) -> BitSet {
|
||||
let num_buckets = num_buckets(max_value);
|
||||
let tinybitsets = vec![TinySet::empty(); num_buckets as usize].into_boxed_slice();
|
||||
let tinybisets = vec![TinySet::empty(); num_buckets as usize].into_boxed_slice();
|
||||
BitSet {
|
||||
tinysets: tinybitsets,
|
||||
tinysets: tinybisets,
|
||||
len: 0,
|
||||
max_value,
|
||||
}
|
||||
@@ -209,15 +227,14 @@ impl BitSet {
|
||||
/// within `[0, max_val)`.
|
||||
pub fn with_max_value_and_full(max_value: u32) -> BitSet {
|
||||
let num_buckets = num_buckets(max_value);
|
||||
let mut tinybitsets = vec![TinySet::full(); num_buckets as usize].into_boxed_slice();
|
||||
let mut tinybisets = vec![TinySet::full(); num_buckets as usize].into_boxed_slice();
|
||||
|
||||
// Fix padding
|
||||
let lower = max_value % 64u32;
|
||||
if lower != 0 {
|
||||
tinybitsets[tinybitsets.len() - 1] = TinySet::range_lower(lower);
|
||||
}
|
||||
tinybisets[tinybisets.len() - 1] = TinySet::range_lower(lower);
|
||||
|
||||
BitSet {
|
||||
tinysets: tinybitsets,
|
||||
tinysets: tinybisets,
|
||||
len: max_value as u64,
|
||||
max_value,
|
||||
}
|
||||
@@ -230,22 +247,7 @@ impl BitSet {
|
||||
}
|
||||
}
|
||||
|
||||
/// Intersect with serialized bitset
|
||||
pub fn intersect_update(&mut self, other: &ReadOnlyBitSet) {
|
||||
self.intersect_update_with_iter(other.iter_tinysets());
|
||||
}
|
||||
|
||||
/// Intersect with tinysets
|
||||
fn intersect_update_with_iter(&mut self, other: impl Iterator<Item = TinySet>) {
|
||||
self.len = 0;
|
||||
for (left, right) in self.tinysets.iter_mut().zip(other) {
|
||||
*left = left.intersect(right);
|
||||
self.len += left.len() as u64;
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of elements in the `BitSet`.
|
||||
#[inline]
|
||||
pub fn len(&self) -> usize {
|
||||
self.len as usize
|
||||
}
|
||||
@@ -295,7 +297,6 @@ impl BitSet {
|
||||
.map(|delta_bucket| bucket + delta_bucket as u32)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn max_value(&self) -> u32 {
|
||||
self.max_value
|
||||
}
|
||||
@@ -310,34 +311,16 @@ impl BitSet {
|
||||
|
||||
/// Serialized BitSet.
|
||||
#[derive(Clone)]
|
||||
pub struct ReadOnlyBitSet {
|
||||
pub struct ReadSerializedBitSet {
|
||||
data: OwnedBytes,
|
||||
max_value: u32,
|
||||
}
|
||||
|
||||
pub fn intersect_bitsets(left: &ReadOnlyBitSet, other: &ReadOnlyBitSet) -> ReadOnlyBitSet {
|
||||
assert_eq!(left.max_value(), other.max_value());
|
||||
assert_eq!(left.data.len(), other.data.len());
|
||||
let union_tinyset_it = left
|
||||
.iter_tinysets()
|
||||
.zip(other.iter_tinysets())
|
||||
.map(|(left_tinyset, right_tinyset)| left_tinyset.intersect(right_tinyset));
|
||||
let mut output_dataset: Vec<u8> = Vec::with_capacity(left.data.len());
|
||||
for tinyset in union_tinyset_it {
|
||||
output_dataset.extend_from_slice(&tinyset.into_bytes());
|
||||
}
|
||||
ReadOnlyBitSet {
|
||||
data: OwnedBytes::new(output_dataset),
|
||||
max_value: left.max_value(),
|
||||
}
|
||||
}
|
||||
|
||||
impl ReadOnlyBitSet {
|
||||
impl ReadSerializedBitSet {
|
||||
pub fn open(data: OwnedBytes) -> Self {
|
||||
let (max_value_data, data) = data.split(4);
|
||||
assert_eq!(data.len() % 8, 0);
|
||||
let max_value: u32 = u32::from_le_bytes(max_value_data.as_ref().try_into().unwrap());
|
||||
ReadOnlyBitSet { data, max_value }
|
||||
ReadSerializedBitSet { data, max_value }
|
||||
}
|
||||
|
||||
/// Number of elements in the bitset.
|
||||
@@ -351,9 +334,10 @@ impl ReadOnlyBitSet {
|
||||
/// Iterate the tinyset on the fly from serialized data.
|
||||
///
|
||||
#[inline]
|
||||
fn iter_tinysets(&self) -> impl Iterator<Item = TinySet> + '_ {
|
||||
fn iter_tinysets<'a>(&'a self) -> impl Iterator<Item = TinySet> + 'a {
|
||||
assert!((self.data.len()) % 8 == 0);
|
||||
self.data.chunks_exact(8).map(move |chunk| {
|
||||
let tinyset: TinySet = TinySet::deserialize(chunk.try_into().unwrap());
|
||||
let tinyset: TinySet = TinySet::deserialize(chunk.try_into().unwrap()).unwrap();
|
||||
tinyset
|
||||
})
|
||||
}
|
||||
@@ -361,7 +345,7 @@ impl ReadOnlyBitSet {
|
||||
/// Iterate over the positions of the elements.
|
||||
///
|
||||
#[inline]
|
||||
pub fn iter(&self) -> impl Iterator<Item = u32> + '_ {
|
||||
pub fn iter<'a>(&'a self) -> impl Iterator<Item = u32> + 'a {
|
||||
self.iter_tinysets()
|
||||
.enumerate()
|
||||
.flat_map(move |(chunk_num, tinyset)| {
|
||||
@@ -391,56 +375,20 @@ impl ReadOnlyBitSet {
|
||||
pub fn max_value(&self) -> u32 {
|
||||
self.max_value
|
||||
}
|
||||
|
||||
/// Number of bytes used in the bitset representation.
|
||||
pub fn num_bytes(&self) -> usize {
|
||||
self.data.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<&'a BitSet> for ReadOnlyBitSet {
|
||||
fn from(bitset: &'a BitSet) -> ReadOnlyBitSet {
|
||||
let mut buffer = Vec::with_capacity(bitset.tinysets.len() * 8 + 4);
|
||||
bitset
|
||||
.serialize(&mut buffer)
|
||||
.expect("serializing into a buffer should never fail");
|
||||
ReadOnlyBitSet::open(OwnedBytes::new(buffer))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::BitSet;
|
||||
use super::ReadOnlyBitSet;
|
||||
use super::ReadSerializedBitSet;
|
||||
use super::TinySet;
|
||||
use ownedbytes::OwnedBytes;
|
||||
use rand::distributions::Bernoulli;
|
||||
use rand::rngs::StdRng;
|
||||
use rand::{Rng, SeedableRng};
|
||||
use std::collections::HashSet;
|
||||
|
||||
#[test]
|
||||
fn test_read_serialized_bitset_full_multi() {
|
||||
for i in 0..1000 {
|
||||
let bitset = BitSet::with_max_value_and_full(i);
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
|
||||
let bitset = ReadOnlyBitSet::open(OwnedBytes::new(out));
|
||||
assert_eq!(bitset.len() as usize, i as usize);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_serialized_bitset_full_block() {
|
||||
let bitset = BitSet::with_max_value_and_full(64);
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
|
||||
let bitset = ReadOnlyBitSet::open(OwnedBytes::new(out));
|
||||
assert_eq!(bitset.len() as usize, 64 as usize);
|
||||
}
|
||||
use std::convert::TryInto;
|
||||
|
||||
#[test]
|
||||
fn test_read_serialized_bitset_full() {
|
||||
@@ -449,50 +397,10 @@ mod tests {
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
|
||||
let bitset = ReadOnlyBitSet::open(OwnedBytes::new(out));
|
||||
let bitset = ReadSerializedBitSet::open(OwnedBytes::new(out));
|
||||
assert_eq!(bitset.len(), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bitset_intersect() {
|
||||
let bitset_serialized = {
|
||||
let mut bitset = BitSet::with_max_value_and_full(5);
|
||||
bitset.remove(1);
|
||||
bitset.remove(3);
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
|
||||
ReadOnlyBitSet::open(OwnedBytes::new(out))
|
||||
};
|
||||
|
||||
let mut bitset = BitSet::with_max_value_and_full(5);
|
||||
bitset.remove(1);
|
||||
bitset.intersect_update(&bitset_serialized);
|
||||
|
||||
assert!(bitset.contains(0));
|
||||
assert!(!bitset.contains(1));
|
||||
assert!(bitset.contains(2));
|
||||
assert!(!bitset.contains(3));
|
||||
assert!(bitset.contains(4));
|
||||
|
||||
bitset.intersect_update_with_iter(vec![TinySet::singleton(0)].into_iter());
|
||||
|
||||
assert!(bitset.contains(0));
|
||||
assert!(!bitset.contains(1));
|
||||
assert!(!bitset.contains(2));
|
||||
assert!(!bitset.contains(3));
|
||||
assert!(!bitset.contains(4));
|
||||
assert_eq!(bitset.len(), 1);
|
||||
|
||||
bitset.intersect_update_with_iter(vec![TinySet::singleton(1)].into_iter());
|
||||
assert!(!bitset.contains(0));
|
||||
assert!(!bitset.contains(1));
|
||||
assert!(!bitset.contains(2));
|
||||
assert!(!bitset.contains(3));
|
||||
assert!(!bitset.contains(4));
|
||||
assert_eq!(bitset.len(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_read_serialized_bitset_empty() {
|
||||
let mut bitset = BitSet::with_max_value(5);
|
||||
@@ -500,14 +408,14 @@ mod tests {
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
|
||||
let bitset = ReadOnlyBitSet::open(OwnedBytes::new(out));
|
||||
let bitset = ReadSerializedBitSet::open(OwnedBytes::new(out));
|
||||
assert_eq!(bitset.len(), 1);
|
||||
|
||||
{
|
||||
let bitset = BitSet::with_max_value(5);
|
||||
let mut out = vec![];
|
||||
bitset.serialize(&mut out).unwrap();
|
||||
let bitset = ReadOnlyBitSet::open(OwnedBytes::new(out));
|
||||
let bitset = ReadSerializedBitSet::open(OwnedBytes::new(out));
|
||||
assert_eq!(bitset.len(), 0);
|
||||
}
|
||||
}
|
||||
@@ -571,9 +479,13 @@ mod tests {
|
||||
assert!(u.pop_lowest().is_none());
|
||||
}
|
||||
{
|
||||
let original = TinySet::empty().insert(63u32).insert(5);
|
||||
let after_serialize_deserialize = TinySet::deserialize(original.into_bytes());
|
||||
assert_eq!(original, after_serialize_deserialize);
|
||||
let u = TinySet::empty().insert(63u32).insert(5);
|
||||
let mut data = vec![];
|
||||
u.serialize(&mut data).unwrap();
|
||||
let mut u = TinySet::deserialize(data[..8].try_into().unwrap()).unwrap();
|
||||
assert_eq!(u.pop_lowest(), Some(5u32));
|
||||
assert_eq!(u.pop_lowest(), Some(63u32));
|
||||
assert!(u.pop_lowest().is_none());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -595,12 +507,12 @@ mod tests {
|
||||
// test deser
|
||||
let mut data = vec![];
|
||||
bitset.serialize(&mut data).unwrap();
|
||||
let ro_bitset = ReadOnlyBitSet::open(OwnedBytes::new(data));
|
||||
let bitset = BitSet::deserialize(&data).unwrap();
|
||||
for el in 0..max_value {
|
||||
assert_eq!(hashset.contains(&el), ro_bitset.contains(el));
|
||||
assert_eq!(hashset.contains(&el), bitset.contains(el));
|
||||
}
|
||||
assert_eq!(ro_bitset.max_value(), max_value);
|
||||
assert_eq!(ro_bitset.len(), els.len());
|
||||
assert_eq!(bitset.max_value(), max_value);
|
||||
assert_eq!(bitset.len(), els.len());
|
||||
};
|
||||
|
||||
test_against_hashset(&[], 0);
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
#![allow(clippy::len_without_is_empty)]
|
||||
|
||||
use std::ops::Deref;
|
||||
|
||||
pub use byteorder::LittleEndian as Endianness;
|
||||
|
||||
@@ -38,7 +38,7 @@ Note: Tantivy 0.16 does not do this optimization yet.
|
||||
In principle there are many algorithms possible that exploit the monotonically increasing nature. (aggregations maybe?)
|
||||
|
||||
## Usage
|
||||
The index sorting can be configured setting [`sort_by_field`](https://github.com/quickwit-inc/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/core/index_meta.rs#L238) on `IndexSettings` and passing it to a `IndexBuilder`. As of tantvy 0.16 only fast fields are allowed to be used.
|
||||
The index sorting can be configured setting [`sort_by_field`](https://github.com/tantivy-search/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/core/index_meta.rs#L238) on `IndexSettings` and passing it to a `IndexBuilder`. As of tantvy 0.16 only fast fields are allowed to be used.
|
||||
|
||||
```
|
||||
let settings = IndexSettings {
|
||||
@@ -55,7 +55,7 @@ let index = index_builder.create_in_ram().unwrap();
|
||||
|
||||
## Implementation details
|
||||
|
||||
Sorting an index is applied in the serialization step. In general there are two serialization steps: [Finishing a single segment](https://github.com/quickwit-inc/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/indexer/segment_writer.rs#L338) and [merging multiple segments](https://github.com/quickwit-inc/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/indexer/merger.rs#L1073).
|
||||
Sorting an index is applied in the serialization step. In general there are two serialization steps: [Finishing a single segment](https://github.com/tantivy-search/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/indexer/segment_writer.rs#L338) and [merging multiple segments](https://github.com/tantivy-search/tantivy/blob/000d76b11a139a84b16b9b95060a1c93e8b9851c/src/indexer/merger.rs#L1073).
|
||||
|
||||
In both cases we generate a docid mapping reflecting the sort. This mapping is used when serializing the different components (doc store, fastfields, posting list, normfield, facets).
|
||||
|
||||
|
||||
@@ -96,7 +96,7 @@ fn main() -> tantivy::Result<()> {
|
||||
);
|
||||
|
||||
// ... and add it to the `IndexWriter`.
|
||||
index_writer.add_document(old_man_doc)?;
|
||||
index_writer.add_document(old_man_doc);
|
||||
|
||||
// For convenience, tantivy also comes with a macro to
|
||||
// reduce the boilerplate above.
|
||||
@@ -110,7 +110,7 @@ fn main() -> tantivy::Result<()> {
|
||||
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 \
|
||||
limbs and branches that arch over the pool"
|
||||
))?;
|
||||
));
|
||||
|
||||
// Multivalued field just need to be repeated.
|
||||
index_writer.add_document(doc!(
|
||||
@@ -120,7 +120,7 @@ fn main() -> tantivy::Result<()> {
|
||||
enterprise which you have regarded with such evil forebodings. I arrived here \
|
||||
yesterday, and my first task is to assure my dear sister of my welfare and \
|
||||
increasing confidence in the success of my undertaking."
|
||||
))?;
|
||||
));
|
||||
|
||||
// This is an example, so we will only index 3 documents
|
||||
// here. You can check out tantivy's tutorial to index
|
||||
|
||||
@@ -145,23 +145,23 @@ fn main() -> tantivy::Result<()> {
|
||||
product_description => "While it is ok for short distance travel, this broom \
|
||||
was designed quiditch. It will up your game.",
|
||||
price => 30_200u64
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
product_name => "Turbulobroom",
|
||||
product_description => "You might have heard of this broom before : it is the sponsor of the Wales team.\
|
||||
You'll enjoy its sharp turns, and rapid acceleration",
|
||||
price => 29_240u64
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
product_name => "Broomio",
|
||||
product_description => "Great value for the price. This broom is a market favorite",
|
||||
price => 21_240u64
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
product_name => "Whack a Mole",
|
||||
product_description => "Prime quality bat.",
|
||||
price => 5_200u64
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -68,7 +68,7 @@ fn main() -> tantivy::Result<()> {
|
||||
title => "The Old Man and the Sea",
|
||||
body => "He was an old man who fished alone in a skiff in the Gulf Stream and \
|
||||
he had gone eighty-four days now without taking a fish."
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "Of Mice and Men",
|
||||
body => r#"A few miles south of Soledad, the Salinas River drops in close to the hillside
|
||||
@@ -79,14 +79,14 @@ fn main() -> tantivy::Result<()> {
|
||||
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
|
||||
limbs and branches that arch over the pool"#
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "Frankenstein",
|
||||
body => r#"You will rejoice to hear that no disaster has accompanied the commencement of an
|
||||
enterprise which you have regarded with such evil forebodings. I arrived here
|
||||
yesterday, and my first task is to assure my dear sister of my welfare and
|
||||
increasing confidence in the success of my undertaking."#
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -76,15 +76,15 @@ fn main() -> tantivy::Result<()> {
|
||||
index_writer.add_document(doc!(
|
||||
isbn => "978-0099908401",
|
||||
title => "The old Man and the see"
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
isbn => "978-0140177398",
|
||||
title => "Of Mice and Men",
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "Frankentein", //< Oops there is a typo here.
|
||||
isbn => "978-9176370711",
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -122,7 +122,7 @@ fn main() -> tantivy::Result<()> {
|
||||
index_writer.add_document(doc!(
|
||||
title => "Frankenstein",
|
||||
isbn => "978-9176370711",
|
||||
))?;
|
||||
));
|
||||
|
||||
// You are guaranteed that your clients will only observe your index in
|
||||
// the state it was in after a commit.
|
||||
|
||||
@@ -35,35 +35,35 @@ fn main() -> tantivy::Result<()> {
|
||||
index_writer.add_document(doc!(
|
||||
name => "Cat",
|
||||
classification => Facet::from("/Felidae/Felinae/Felis")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Canada lynx",
|
||||
classification => Facet::from("/Felidae/Felinae/Lynx")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Cheetah",
|
||||
classification => Facet::from("/Felidae/Felinae/Acinonyx")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Tiger",
|
||||
classification => Facet::from("/Felidae/Pantherinae/Panthera")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Lion",
|
||||
classification => Facet::from("/Felidae/Pantherinae/Panthera")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Jaguar",
|
||||
classification => Facet::from("/Felidae/Pantherinae/Panthera")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Sunda clouded leopard",
|
||||
classification => Facet::from("/Felidae/Pantherinae/Neofelis")
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
name => "Fossa",
|
||||
classification => Facet::from("/Eupleridae/Cryptoprocta")
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -20,14 +20,14 @@ fn main() -> tantivy::Result<()> {
|
||||
title => "Fried egg",
|
||||
ingredient => Facet::from("/ingredient/egg"),
|
||||
ingredient => Facet::from("/ingredient/oil"),
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "Scrambled egg",
|
||||
ingredient => Facet::from("/ingredient/egg"),
|
||||
ingredient => Facet::from("/ingredient/butter"),
|
||||
ingredient => Facet::from("/ingredient/milk"),
|
||||
ingredient => Facet::from("/ingredient/salt"),
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "Egg rolls",
|
||||
ingredient => Facet::from("/ingredient/egg"),
|
||||
@@ -36,7 +36,7 @@ fn main() -> tantivy::Result<()> {
|
||||
ingredient => Facet::from("/ingredient/oil"),
|
||||
ingredient => Facet::from("/ingredient/tortilla-wrap"),
|
||||
ingredient => Facet::from("/ingredient/mushroom"),
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -7,7 +7,7 @@ use tantivy::query::RangeQuery;
|
||||
use tantivy::schema::{Schema, INDEXED};
|
||||
use tantivy::{doc, Index, Result};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
fn run() -> Result<()> {
|
||||
// For the sake of simplicity, this schema will only have 1 field
|
||||
let mut schema_builder = Schema::builder();
|
||||
|
||||
@@ -19,7 +19,7 @@ fn main() -> Result<()> {
|
||||
{
|
||||
let mut index_writer = index.writer_with_num_threads(1, 6_000_000)?;
|
||||
for year in 1950u64..2019u64 {
|
||||
index_writer.add_document(doc!(year_field => year))?;
|
||||
index_writer.add_document(doc!(year_field => year));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
// The index will be a range of years
|
||||
@@ -33,3 +33,7 @@ fn main() -> Result<()> {
|
||||
assert_eq!(num_60s_books, 10);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn main() {
|
||||
run().unwrap()
|
||||
}
|
||||
|
||||
@@ -25,9 +25,9 @@ fn main() -> tantivy::Result<()> {
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
let mut index_writer = index.writer_with_num_threads(1, 50_000_000)?;
|
||||
index_writer.add_document(doc!(title => "The Old Man and the Sea"))?;
|
||||
index_writer.add_document(doc!(title => "Of Mice and Men"))?;
|
||||
index_writer.add_document(doc!(title => "The modern Promotheus"))?;
|
||||
index_writer.add_document(doc!(title => "The Old Man and the Sea"));
|
||||
index_writer.add_document(doc!(title => "Of Mice and Men"));
|
||||
index_writer.add_document(doc!(title => "The modern Promotheus"));
|
||||
index_writer.commit()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
|
||||
@@ -29,7 +29,7 @@ use std::sync::{Arc, RwLock};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
use tantivy::schema::{Schema, STORED, TEXT};
|
||||
use tantivy::{doc, Index, IndexWriter, Opstamp, TantivyError};
|
||||
use tantivy::{doc, Index, IndexWriter, Opstamp};
|
||||
|
||||
fn main() -> tantivy::Result<()> {
|
||||
// # Defining the schema
|
||||
@@ -59,11 +59,10 @@ fn main() -> tantivy::Result<()> {
|
||||
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 \
|
||||
limbs and branches that arch over the pool"
|
||||
))?;
|
||||
));
|
||||
println!("add doc {} from thread 1 - opstamp {}", i, opstamp);
|
||||
thread::sleep(Duration::from_millis(20));
|
||||
}
|
||||
Result::<(), TantivyError>::Ok(())
|
||||
});
|
||||
|
||||
// # Second indexing thread.
|
||||
@@ -79,12 +78,11 @@ fn main() -> tantivy::Result<()> {
|
||||
index_writer_rlock.add_document(doc!(
|
||||
title => "Manufacturing consent",
|
||||
body => "Some great book description..."
|
||||
))?
|
||||
))
|
||||
};
|
||||
println!("add doc {} from thread 2 - opstamp {}", i, opstamp);
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
}
|
||||
Result::<(), TantivyError>::Ok(())
|
||||
});
|
||||
|
||||
// # In the main thread, we commit 10 times, once every 500ms.
|
||||
@@ -92,7 +90,7 @@ fn main() -> tantivy::Result<()> {
|
||||
let opstamp: Opstamp = {
|
||||
// Committing or rollbacking on the other hand requires write lock. This will block other threads.
|
||||
let mut index_writer_wlock = index_writer.write().unwrap();
|
||||
index_writer_wlock.commit()?
|
||||
index_writer_wlock.commit().unwrap()
|
||||
};
|
||||
println!("committed with opstamp {}", opstamp);
|
||||
thread::sleep(Duration::from_millis(500));
|
||||
|
||||
@@ -68,7 +68,7 @@ fn main() -> tantivy::Result<()> {
|
||||
let old_man_doc = doc!(title => title_tok, body => body_tok);
|
||||
|
||||
// ... now let's just add it to the IndexWriter
|
||||
index_writer.add_document(old_man_doc)?;
|
||||
index_writer.add_document(old_man_doc);
|
||||
|
||||
// Pretokenized text can also be fed as JSON
|
||||
let short_man_json = r#"{
|
||||
@@ -84,7 +84,7 @@ fn main() -> tantivy::Result<()> {
|
||||
|
||||
let short_man_doc = schema.parse_document(short_man_json)?;
|
||||
|
||||
index_writer.add_document(short_man_doc)?;
|
||||
index_writer.add_document(short_man_doc);
|
||||
|
||||
// Let's commit changes
|
||||
index_writer.commit()?;
|
||||
@@ -106,7 +106,9 @@ fn main() -> tantivy::Result<()> {
|
||||
IndexRecordOption::Basic,
|
||||
);
|
||||
|
||||
let (top_docs, count) = searcher.search(&query, &(TopDocs::with_limit(2), Count))?;
|
||||
let (top_docs, count) = searcher
|
||||
.search(&query, &(TopDocs::with_limit(2), Count))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(count, 2);
|
||||
|
||||
@@ -127,7 +129,9 @@ fn main() -> tantivy::Result<()> {
|
||||
IndexRecordOption::Basic,
|
||||
);
|
||||
|
||||
let (_top_docs, count) = searcher.search(&query, &(TopDocs::with_limit(2), Count))?;
|
||||
let (_top_docs, count) = searcher
|
||||
.search(&query, &(TopDocs::with_limit(2), Count))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(count, 0);
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ fn main() -> tantivy::Result<()> {
|
||||
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 \
|
||||
limbs and branches that arch over the pool"
|
||||
))?;
|
||||
));
|
||||
// ...
|
||||
index_writer.commit()?;
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ fn main() -> tantivy::Result<()> {
|
||||
title => "The Old Man and the Sea",
|
||||
body => "He was an old man who fished alone in a skiff in the Gulf Stream and \
|
||||
he had gone eighty-four days now without taking a fish."
|
||||
))?;
|
||||
));
|
||||
|
||||
index_writer.add_document(doc!(
|
||||
title => "Of Mice and Men",
|
||||
@@ -80,7 +80,7 @@ fn main() -> tantivy::Result<()> {
|
||||
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 \
|
||||
limbs and branches that arch over the pool"
|
||||
))?;
|
||||
));
|
||||
|
||||
index_writer.add_document(doc!(
|
||||
title => "Frankenstein",
|
||||
@@ -88,7 +88,7 @@ fn main() -> tantivy::Result<()> {
|
||||
enterprise which you have regarded with such evil forebodings. I arrived here \
|
||||
yesterday, and my first task is to assure my dear sister of my welfare and \
|
||||
increasing confidence in the success of my undertaking."
|
||||
))?;
|
||||
));
|
||||
|
||||
index_writer.commit()?;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
authors = ["Paul Masurel <paul@quickwit.io>", "Pascal Seitz <pascal@quickwit.io>"]
|
||||
name = "ownedbytes"
|
||||
version = "0.2.0"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
description = "Expose data as static slice"
|
||||
license = "MIT"
|
||||
|
||||
@@ -76,19 +76,6 @@ impl OwnedBytes {
|
||||
(left, right)
|
||||
}
|
||||
|
||||
/// Splits the right part of the `OwnedBytes` at the given offset.
|
||||
///
|
||||
/// `self` is truncated to `split_len`, left with the remaining bytes.
|
||||
pub fn split_off(&mut self, split_len: usize) -> OwnedBytes {
|
||||
let right_box_stable_deref = self.box_stable_deref.clone();
|
||||
let right_piece = OwnedBytes {
|
||||
data: &self.data[split_len..],
|
||||
box_stable_deref: right_box_stable_deref,
|
||||
};
|
||||
self.data = &self.data[..split_len];
|
||||
right_piece
|
||||
}
|
||||
|
||||
/// Returns true iff this `OwnedBytes` is empty.
|
||||
#[inline]
|
||||
pub fn is_empty(&self) -> bool {
|
||||
@@ -137,35 +124,6 @@ impl fmt::Debug for OwnedBytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for OwnedBytes {
|
||||
fn eq(&self, other: &OwnedBytes) -> bool {
|
||||
self.as_slice() == other.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for OwnedBytes {}
|
||||
|
||||
impl PartialEq<[u8]> for OwnedBytes {
|
||||
fn eq(&self, other: &[u8]) -> bool {
|
||||
self.as_slice() == other
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq<str> for OwnedBytes {
|
||||
fn eq(&self, other: &str) -> bool {
|
||||
self.as_slice() == other.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: ?Sized> PartialEq<&'a T> for OwnedBytes
|
||||
where
|
||||
OwnedBytes: PartialEq<T>,
|
||||
{
|
||||
fn eq(&self, other: &&'a T) -> bool {
|
||||
*self == **other
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for OwnedBytes {
|
||||
type Target = [u8];
|
||||
|
||||
@@ -329,14 +287,4 @@ mod tests {
|
||||
assert_eq!(right.as_slice(), b"");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_split_off() {
|
||||
let mut data = OwnedBytes::new(b"abcdef".as_ref());
|
||||
assert_eq!(data, "abcdef");
|
||||
assert_eq!(data.split_off(2), "cdef");
|
||||
assert_eq!(data, "ab");
|
||||
assert_eq!(data.split_off(1), "b");
|
||||
assert_eq!(data, "a");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,9 @@ authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||
license = "MIT"
|
||||
categories = ["database-implementations", "data-structures"]
|
||||
description = """Search engine library"""
|
||||
documentation = "https://quickwit-inc.github.io/tantivy/tantivy/index.html"
|
||||
homepage = "https://github.com/quickwit-inc/tantivy"
|
||||
repository = "https://github.com/quickwit-inc/tantivy"
|
||||
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"
|
||||
|
||||
@@ -20,10 +20,10 @@ use crate::SegmentReader;
|
||||
/// let index = Index::create_in_ram(schema);
|
||||
///
|
||||
/// let mut index_writer = index.writer(3_000_000).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind")).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib")).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow")).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl")).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"));
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"));
|
||||
/// assert!(index_writer.commit().is_ok());
|
||||
///
|
||||
/// let reader = index.reader().unwrap();
|
||||
|
||||
@@ -103,23 +103,23 @@ fn facet_depth(facet_bytes: &[u8]) -> usize {
|
||||
/// title => "The Name of the Wind",
|
||||
/// facet => Facet::from("/lang/en"),
|
||||
/// facet => Facet::from("/category/fiction/fantasy")
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "Dune",
|
||||
/// facet => Facet::from("/lang/en"),
|
||||
/// facet => Facet::from("/category/fiction/sci-fi")
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "La Vénus d'Ille",
|
||||
/// facet => Facet::from("/lang/fr"),
|
||||
/// facet => Facet::from("/category/fiction/fantasy"),
|
||||
/// facet => Facet::from("/category/fiction/horror")
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of a Young Girl",
|
||||
/// facet => Facet::from("/lang/en"),
|
||||
/// facet => Facet::from("/category/biography")
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.commit()?;
|
||||
/// }
|
||||
/// let reader = index.reader()?;
|
||||
@@ -470,13 +470,13 @@ mod tests {
|
||||
use std::iter;
|
||||
|
||||
#[test]
|
||||
fn test_facet_collector_drilldown() -> crate::Result<()> {
|
||||
fn test_facet_collector_drilldown() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let facet_field = schema_builder.add_facet_field("facet", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let num_facets: usize = 3 * 4 * 5;
|
||||
let facets: Vec<Facet> = (0..num_facets)
|
||||
.map(|mut n| {
|
||||
@@ -491,14 +491,14 @@ mod tests {
|
||||
for i in 0..num_facets * 10 {
|
||||
let mut doc = Document::new();
|
||||
doc.add_facet(facet_field, facets[i % num_facets].clone());
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
index_writer.commit().unwrap();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let mut facet_collector = FacetCollector::for_field(facet_field);
|
||||
facet_collector.add_facet(Facet::from("/top1"));
|
||||
let counts = searcher.search(&AllQuery, &facet_collector)?;
|
||||
let counts = searcher.search(&AllQuery, &facet_collector).unwrap();
|
||||
|
||||
{
|
||||
let facets: Vec<(String, u64)> = counts
|
||||
@@ -518,7 +518,6 @@ mod tests {
|
||||
.collect::<Vec<_>>()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -531,28 +530,27 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_doc_unsorted_multifacet() -> crate::Result<()> {
|
||||
fn test_doc_unsorted_multifacet() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let facet_field = schema_builder.add_facet_field("facets", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from_text(&"/subjects/A/a").unwrap(),
|
||||
facet_field => Facet::from_text(&"/subjects/B/a").unwrap(),
|
||||
facet_field => Facet::from_text(&"/subjects/A/b").unwrap(),
|
||||
facet_field => Facet::from_text(&"/subjects/B/b").unwrap(),
|
||||
))?;
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
));
|
||||
index_writer.commit().unwrap();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
assert_eq!(searcher.num_docs(), 1);
|
||||
let mut facet_collector = FacetCollector::for_field(facet_field);
|
||||
facet_collector.add_facet("/subjects");
|
||||
let counts = searcher.search(&AllQuery, &facet_collector)?;
|
||||
let counts = searcher.search(&AllQuery, &facet_collector).unwrap();
|
||||
let facets: Vec<(&Facet, u64)> = counts.get("/subjects").collect();
|
||||
assert_eq!(facets[0].1, 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -564,16 +562,16 @@ mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from_text(&"/A/A").unwrap(),
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from_text(&"/A/B").unwrap(),
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from_text(&"/A/C/A").unwrap(),
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from_text(&"/D/C/A").unwrap(),
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -639,7 +637,7 @@ mod tests {
|
||||
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
for doc in docs {
|
||||
index_writer.add_document(doc).unwrap();
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit().unwrap();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
@@ -679,7 +677,7 @@ mod tests {
|
||||
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
for doc in docs {
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
|
||||
@@ -25,37 +25,34 @@ use crate::{Score, SegmentReader, TantivyError};
|
||||
/// use tantivy::schema::{Schema, TEXT, INDEXED, FAST};
|
||||
/// use tantivy::{doc, DocAddress, Index};
|
||||
///
|
||||
/// # fn main() -> tantivy::Result<()> {
|
||||
/// let mut schema_builder = Schema::builder();
|
||||
/// let title = schema_builder.add_text_field("title", TEXT);
|
||||
/// let price = schema_builder.add_u64_field("price", INDEXED | FAST);
|
||||
/// let schema = schema_builder.build();
|
||||
/// let index = Index::create_in_ram(schema);
|
||||
///
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind", price => 30_200u64))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib", price => 29_240u64))?;
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow", price => 21_240u64))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl", price => 20_120u64))?;
|
||||
/// index_writer.commit()?;
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind", price => 30_200u64));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib", price => 29_240u64));
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow", price => 21_240u64));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl", price => 20_120u64));
|
||||
/// assert!(index_writer.commit().is_ok());
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
/// let reader = index.reader().unwrap();
|
||||
/// let searcher = reader.searcher();
|
||||
///
|
||||
/// let query_parser = QueryParser::for_index(&index, vec![title]);
|
||||
/// let query = query_parser.parse_query("diary")?;
|
||||
/// let query = query_parser.parse_query("diary").unwrap();
|
||||
/// let no_filter_collector = FilterCollector::new(price, &|value: u64| value > 20_120u64, TopDocs::with_limit(2));
|
||||
/// let top_docs = searcher.search(&query, &no_filter_collector)?;
|
||||
/// let top_docs = searcher.search(&query, &no_filter_collector).unwrap();
|
||||
///
|
||||
/// assert_eq!(top_docs.len(), 1);
|
||||
/// assert_eq!(top_docs[0].1, DocAddress::new(0, 1));
|
||||
///
|
||||
/// let filter_all_collector: FilterCollector<_, _, u64> = FilterCollector::new(price, &|value| value < 5u64, TopDocs::with_limit(2));
|
||||
/// let filtered_top_docs = searcher.search(&query, &filter_all_collector)?;
|
||||
/// let filtered_top_docs = searcher.search(&query, &filter_all_collector).unwrap();
|
||||
///
|
||||
/// assert_eq!(filtered_top_docs.len(), 0);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct FilterCollector<TCollector, TPredicate, TPredicateValue: FastValue>
|
||||
where
|
||||
|
||||
@@ -226,10 +226,10 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut writer = index.writer_with_num_threads(1, 4_000_000)?;
|
||||
writer.add_document(doc!(val_field=>12i64))?;
|
||||
writer.add_document(doc!(val_field=>-30i64))?;
|
||||
writer.add_document(doc!(val_field=>-12i64))?;
|
||||
writer.add_document(doc!(val_field=>-10i64))?;
|
||||
writer.add_document(doc!(val_field=>12i64));
|
||||
writer.add_document(doc!(val_field=>-30i64));
|
||||
writer.add_document(doc!(val_field=>-12i64));
|
||||
writer.add_document(doc!(val_field=>-10i64));
|
||||
writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -247,13 +247,13 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut writer = index.writer_with_num_threads(1, 4_000_000)?;
|
||||
writer.add_document(doc!(val_field=>12i64))?;
|
||||
writer.add_document(doc!(val_field=>12i64));
|
||||
writer.commit()?;
|
||||
writer.add_document(doc!(val_field=>-30i64))?;
|
||||
writer.add_document(doc!(val_field=>-30i64));
|
||||
writer.commit()?;
|
||||
writer.add_document(doc!(val_field=>-12i64))?;
|
||||
writer.add_document(doc!(val_field=>-12i64));
|
||||
writer.commit()?;
|
||||
writer.add_document(doc!(val_field=>-10i64))?;
|
||||
writer.add_document(doc!(val_field=>-10i64));
|
||||
writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -271,9 +271,9 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut writer = index.writer_with_num_threads(1, 4_000_000)?;
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1982, 9, 17).and_hms(0, 0,0)))?;
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1986, 3, 9).and_hms(0, 0, 0)))?;
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1983, 9, 27).and_hms(0, 0, 0)))?;
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1982, 9, 17).and_hms(0, 0,0)));
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1986, 3, 9).and_hms(0, 0, 0)));
|
||||
writer.add_document(doc!(date_field=>Utc.ymd(1983, 9, 27).and_hms(0, 0, 0)));
|
||||
writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
|
||||
@@ -48,10 +48,10 @@ use tantivy::collector::{Count, TopDocs};
|
||||
# let mut index_writer = index.writer(3_000_000)?;
|
||||
# index_writer.add_document(doc!(
|
||||
# title => "The Name of the Wind",
|
||||
# ))?;
|
||||
# ));
|
||||
# index_writer.add_document(doc!(
|
||||
# title => "The Diary of Muadib",
|
||||
# ))?;
|
||||
# ));
|
||||
# index_writer.commit()?;
|
||||
# let reader = index.reader()?;
|
||||
# let searcher = reader.searcher();
|
||||
|
||||
@@ -112,19 +112,19 @@ impl<TFruit: Fruit> FruitHandle<TFruit> {
|
||||
/// use tantivy::schema::{Schema, TEXT};
|
||||
/// use tantivy::{doc, Index};
|
||||
///
|
||||
/// # fn main() -> tantivy::Result<()> {
|
||||
/// let mut schema_builder = Schema::builder();
|
||||
/// let title = schema_builder.add_text_field("title", TEXT);
|
||||
/// let schema = schema_builder.build();
|
||||
/// let index = Index::create_in_ram(schema);
|
||||
/// let mut index_writer = index.writer(3_000_000)?;
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"))?;
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"))?;
|
||||
/// index_writer.commit()?;
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
/// let mut index_writer = index.writer(3_000_000).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"));
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"));
|
||||
/// assert!(index_writer.commit().is_ok());
|
||||
///
|
||||
/// let reader = index.reader().unwrap();
|
||||
/// let searcher = reader.searcher();
|
||||
///
|
||||
/// let mut collectors = MultiCollector::new();
|
||||
@@ -139,8 +139,6 @@ impl<TFruit: Fruit> FruitHandle<TFruit> {
|
||||
///
|
||||
/// assert_eq!(count, 2);
|
||||
/// assert_eq!(top_docs.len(), 2);
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
#[allow(clippy::type_complexity)]
|
||||
#[derive(Default)]
|
||||
@@ -254,24 +252,24 @@ mod tests {
|
||||
use crate::Term;
|
||||
|
||||
#[test]
|
||||
fn test_multi_collector() -> crate::Result<()> {
|
||||
fn test_multi_collector() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text=>"abc"))?;
|
||||
index_writer.add_document(doc!(text=>"abc abc abc"))?;
|
||||
index_writer.add_document(doc!(text=>"abc abc"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text=>""))?;
|
||||
index_writer.add_document(doc!(text=>"abc abc abc abc"))?;
|
||||
index_writer.add_document(doc!(text=>"abc"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text=>"abc"));
|
||||
index_writer.add_document(doc!(text=>"abc abc abc"));
|
||||
index_writer.add_document(doc!(text=>"abc abc"));
|
||||
index_writer.commit().unwrap();
|
||||
index_writer.add_document(doc!(text=>""));
|
||||
index_writer.add_document(doc!(text=>"abc abc abc abc"));
|
||||
index_writer.add_document(doc!(text=>"abc"));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let term = Term::from_field_text(text, "abc");
|
||||
let query = TermQuery::new(term, IndexRecordOption::Basic);
|
||||
|
||||
@@ -282,6 +280,5 @@ mod tests {
|
||||
|
||||
assert_eq!(count_handler.extract(&mut multifruits), 5);
|
||||
assert_eq!(topdocs_handler.extract(&mut multifruits).len(), 2);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ pub const TEST_COLLECTOR_WITHOUT_SCORE: TestCollector = TestCollector {
|
||||
};
|
||||
|
||||
#[test]
|
||||
pub fn test_filter_collector() -> crate::Result<()> {
|
||||
pub fn test_filter_collector() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let title = schema_builder.add_text_field("title", TEXT);
|
||||
let price = schema_builder.add_u64_field("price", FAST);
|
||||
@@ -33,25 +33,25 @@ pub fn test_filter_collector() -> crate::Result<()> {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
index_writer.add_document(doc!(title => "The Name of the Wind", price => 30_200u64, date => DateTime::from_str("1898-04-09T00:00:00+00:00").unwrap()))?;
|
||||
index_writer.add_document(doc!(title => "The Diary of Muadib", price => 29_240u64, date => DateTime::from_str("2020-04-09T00:00:00+00:00").unwrap()))?;
|
||||
index_writer.add_document(doc!(title => "The Diary of Anne Frank", price => 18_240u64, date => DateTime::from_str("2019-04-20T00:00:00+00:00").unwrap()))?;
|
||||
index_writer.add_document(doc!(title => "A Dairy Cow", price => 21_240u64, date => DateTime::from_str("2019-04-09T00:00:00+00:00").unwrap()))?;
|
||||
index_writer.add_document(doc!(title => "The Diary of a Young Girl", price => 20_120u64, date => DateTime::from_str("2018-04-09T00:00:00+00:00").unwrap()))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_with_num_threads(1, 10_000_000).unwrap();
|
||||
index_writer.add_document(doc!(title => "The Name of the Wind", price => 30_200u64, date => DateTime::from_str("1898-04-09T00:00:00+00:00").unwrap()));
|
||||
index_writer.add_document(doc!(title => "The Diary of Muadib", price => 29_240u64, date => DateTime::from_str("2020-04-09T00:00:00+00:00").unwrap()));
|
||||
index_writer.add_document(doc!(title => "The Diary of Anne Frank", price => 18_240u64, date => DateTime::from_str("2019-04-20T00:00:00+00:00").unwrap()));
|
||||
index_writer.add_document(doc!(title => "A Dairy Cow", price => 21_240u64, date => DateTime::from_str("2019-04-09T00:00:00+00:00").unwrap()));
|
||||
index_writer.add_document(doc!(title => "The Diary of a Young Girl", price => 20_120u64, date => DateTime::from_str("2018-04-09T00:00:00+00:00").unwrap()));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
|
||||
let query_parser = QueryParser::for_index(&index, vec![title]);
|
||||
let query = query_parser.parse_query("diary")?;
|
||||
let query = query_parser.parse_query("diary").unwrap();
|
||||
let filter_some_collector = FilterCollector::new(
|
||||
price,
|
||||
&|value: u64| value > 20_120u64,
|
||||
TopDocs::with_limit(2),
|
||||
);
|
||||
let top_docs = searcher.search(&query, &filter_some_collector)?;
|
||||
let top_docs = searcher.search(&query, &filter_some_collector).unwrap();
|
||||
|
||||
assert_eq!(top_docs.len(), 1);
|
||||
assert_eq!(top_docs[0].1, DocAddress::new(0, 1));
|
||||
@@ -67,10 +67,9 @@ pub fn test_filter_collector() -> crate::Result<()> {
|
||||
}
|
||||
|
||||
let filter_dates_collector = FilterCollector::new(date, &date_filter, TopDocs::with_limit(5));
|
||||
let filtered_date_docs = searcher.search(&query, &filter_dates_collector)?;
|
||||
let filtered_date_docs = searcher.search(&query, &filter_dates_collector).unwrap();
|
||||
|
||||
assert_eq!(filtered_date_docs.len(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Stores all of the doc ids.
|
||||
@@ -275,8 +274,8 @@ fn make_test_searcher() -> crate::Result<crate::LeasedItem<Searcher>> {
|
||||
let schema = Schema::builder().build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(Document::default())?;
|
||||
index_writer.add_document(Document::default())?;
|
||||
index_writer.add_document(Document::default());
|
||||
index_writer.add_document(Document::default());
|
||||
index_writer.commit()?;
|
||||
Ok(index.reader()?.searcher())
|
||||
}
|
||||
|
||||
@@ -70,7 +70,9 @@ where
|
||||
/// # Panics
|
||||
/// The method panics if limit is 0
|
||||
pub fn with_limit(limit: usize) -> TopCollector<T> {
|
||||
assert!(limit >= 1, "Limit must be strictly greater than 0.");
|
||||
if limit < 1 {
|
||||
panic!("Limit must be strictly greater than 0.");
|
||||
}
|
||||
Self {
|
||||
limit,
|
||||
offset: 0,
|
||||
|
||||
@@ -94,30 +94,27 @@ where
|
||||
/// use tantivy::schema::{Schema, TEXT};
|
||||
/// use tantivy::{doc, DocAddress, Index};
|
||||
///
|
||||
/// # fn main() -> tantivy::Result<()> {
|
||||
/// let mut schema_builder = Schema::builder();
|
||||
/// let title = schema_builder.add_text_field("title", TEXT);
|
||||
/// let schema = schema_builder.build();
|
||||
/// let index = Index::create_in_ram(schema);
|
||||
///
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"))?;
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"))?;
|
||||
/// index_writer.commit()?;
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"));
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"));
|
||||
/// assert!(index_writer.commit().is_ok());
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
/// let reader = index.reader().unwrap();
|
||||
/// let searcher = reader.searcher();
|
||||
///
|
||||
/// let query_parser = QueryParser::for_index(&index, vec![title]);
|
||||
/// let query = query_parser.parse_query("diary")?;
|
||||
/// let top_docs = searcher.search(&query, &TopDocs::with_limit(2))?;
|
||||
/// let query = query_parser.parse_query("diary").unwrap();
|
||||
/// let top_docs = searcher.search(&query, &TopDocs::with_limit(2)).unwrap();
|
||||
///
|
||||
/// assert_eq!(top_docs[0].1, DocAddress::new(0, 1));
|
||||
/// assert_eq!(top_docs[1].1, DocAddress::new(0, 3));
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub struct TopDocs(TopCollector<Score>);
|
||||
|
||||
@@ -183,32 +180,29 @@ impl TopDocs {
|
||||
/// use tantivy::schema::{Schema, TEXT};
|
||||
/// use tantivy::{doc, DocAddress, Index};
|
||||
///
|
||||
/// # fn main() -> tantivy::Result<()> {
|
||||
/// let mut schema_builder = Schema::builder();
|
||||
/// let title = schema_builder.add_text_field("title", TEXT);
|
||||
/// let schema = schema_builder.build();
|
||||
/// let index = Index::create_in_ram(schema);
|
||||
///
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"))?;
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"))?;
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Lena Mukhina"))?;
|
||||
/// index_writer.commit()?;
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000).unwrap();
|
||||
/// index_writer.add_document(doc!(title => "The Name of the Wind"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Muadib"));
|
||||
/// index_writer.add_document(doc!(title => "A Dairy Cow"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of a Young Girl"));
|
||||
/// index_writer.add_document(doc!(title => "The Diary of Lena Mukhina"));
|
||||
/// assert!(index_writer.commit().is_ok());
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
/// let reader = index.reader().unwrap();
|
||||
/// let searcher = reader.searcher();
|
||||
///
|
||||
/// let query_parser = QueryParser::for_index(&index, vec![title]);
|
||||
/// let query = query_parser.parse_query("diary")?;
|
||||
/// let top_docs = searcher.search(&query, &TopDocs::with_limit(2).and_offset(1))?;
|
||||
/// let query = query_parser.parse_query("diary").unwrap();
|
||||
/// let top_docs = searcher.search(&query, &TopDocs::with_limit(2).and_offset(1)).unwrap();
|
||||
///
|
||||
/// assert_eq!(top_docs.len(), 2);
|
||||
/// assert_eq!(top_docs[0].1, DocAddress::new(0, 4));
|
||||
/// assert_eq!(top_docs[1].1, DocAddress::new(0, 3));
|
||||
/// Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn and_offset(self, offset: usize) -> TopDocs {
|
||||
TopDocs(self.0.and_offset(offset))
|
||||
@@ -240,11 +234,11 @@ impl TopDocs {
|
||||
/// #
|
||||
/// # let index = Index::create_in_ram(schema);
|
||||
/// # let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// # index_writer.add_document(doc!(title => "The Name of the Wind", rating => 92u64))?;
|
||||
/// # index_writer.add_document(doc!(title => "The Diary of Muadib", rating => 97u64))?;
|
||||
/// # index_writer.add_document(doc!(title => "A Dairy Cow", rating => 63u64))?;
|
||||
/// # index_writer.add_document(doc!(title => "The Diary of a Young Girl", rating => 80u64))?;
|
||||
/// # index_writer.commit()?;
|
||||
/// # index_writer.add_document(doc!(title => "The Name of the Wind", rating => 92u64));
|
||||
/// # index_writer.add_document(doc!(title => "The Diary of Muadib", rating => 97u64));
|
||||
/// # index_writer.add_document(doc!(title => "A Dairy Cow", rating => 63u64));
|
||||
/// # index_writer.add_document(doc!(title => "The Diary of a Young Girl", rating => 80u64));
|
||||
/// # assert!(index_writer.commit().is_ok());
|
||||
/// # let reader = index.reader()?;
|
||||
/// # let query = QueryParser::for_index(&index, vec![title]).parse_query("diary")?;
|
||||
/// # let top_docs = docs_sorted_by_rating(&reader.searcher(), &query, rating)?;
|
||||
@@ -322,9 +316,9 @@ impl TopDocs {
|
||||
/// #
|
||||
/// # let index = Index::create_in_ram(schema);
|
||||
/// # let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// # index_writer.add_document(doc!(title => "MadCow Inc.", rating => 92_000_000i64))?;
|
||||
/// # index_writer.add_document(doc!(title => "Zozo Cow KKK", rating => 119_000_000i64))?;
|
||||
/// # index_writer.add_document(doc!(title => "Declining Cow", rating => -63_000_000i64))?;
|
||||
/// # index_writer.add_document(doc!(title => "MadCow Inc.", rating => 92_000_000i64));
|
||||
/// # index_writer.add_document(doc!(title => "Zozo Cow KKK", rating => 119_000_000i64));
|
||||
/// # index_writer.add_document(doc!(title => "Declining Cow", rating => -63_000_000i64));
|
||||
/// # assert!(index_writer.commit().is_ok());
|
||||
/// # let reader = index.reader()?;
|
||||
/// # let top_docs = docs_sorted_by_revenue(&reader.searcher(), &AllQuery, rating)?;
|
||||
@@ -423,9 +417,9 @@ impl TopDocs {
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
/// let product_name = index.schema().get_field("product_name").unwrap();
|
||||
/// let popularity: Field = index.schema().get_field("popularity").unwrap();
|
||||
/// index_writer.add_document(doc!(product_name => "The Diary of Muadib", popularity => 1u64))?;
|
||||
/// index_writer.add_document(doc!(product_name => "A Dairy Cow", popularity => 10u64))?;
|
||||
/// index_writer.add_document(doc!(product_name => "The Diary of a Young Girl", popularity => 15u64))?;
|
||||
/// index_writer.add_document(doc!(product_name => "The Diary of Muadib", popularity => 1u64));
|
||||
/// index_writer.add_document(doc!(product_name => "A Dairy Cow", popularity => 10u64));
|
||||
/// index_writer.add_document(doc!(product_name => "The Diary of a Young Girl", popularity => 15u64));
|
||||
/// index_writer.commit()?;
|
||||
/// Ok(index)
|
||||
/// }
|
||||
@@ -533,9 +527,9 @@ impl TopDocs {
|
||||
/// #
|
||||
/// let popularity: Field = index.schema().get_field("popularity").unwrap();
|
||||
/// let boosted: Field = index.schema().get_field("boosted").unwrap();
|
||||
/// # index_writer.add_document(doc!(boosted=>1u64, product_name => "The Diary of Muadib", popularity => 1u64))?;
|
||||
/// # index_writer.add_document(doc!(boosted=>0u64, product_name => "A Dairy Cow", popularity => 10u64))?;
|
||||
/// # index_writer.add_document(doc!(boosted=>0u64, product_name => "The Diary of a Young Girl", popularity => 15u64))?;
|
||||
/// # index_writer.add_document(doc!(boosted=>1u64, product_name => "The Diary of Muadib", popularity => 1u64));
|
||||
/// # index_writer.add_document(doc!(boosted=>0u64, product_name => "A Dairy Cow", popularity => 10u64));
|
||||
/// # index_writer.add_document(doc!(boosted=>0u64, product_name => "The Diary of a Young Girl", popularity => 15u64));
|
||||
/// # index_writer.commit()?;
|
||||
/// // ...
|
||||
/// # let user_query = "diary";
|
||||
@@ -719,18 +713,20 @@ mod tests {
|
||||
use crate::Score;
|
||||
use crate::{DocAddress, DocId, SegmentReader};
|
||||
|
||||
fn make_index() -> crate::Result<Index> {
|
||||
fn make_index() -> Index {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_with_num_threads(1, 10_000_000)?;
|
||||
index_writer.add_document(doc!(text_field=>"Hello happy tax payer."))?;
|
||||
index_writer.add_document(doc!(text_field=>"Droopy says hello happy tax payer"))?;
|
||||
index_writer.add_document(doc!(text_field=>"I like Droopy"))?;
|
||||
index_writer.commit()?;
|
||||
Ok(index)
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_with_num_threads(1, 10_000_000).unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"Hello happy tax payer."));
|
||||
index_writer.add_document(doc!(text_field=>"Droopy says hello happy tax payer"));
|
||||
index_writer.add_document(doc!(text_field=>"I like Droopy"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
index
|
||||
}
|
||||
|
||||
fn assert_results_equals(results: &[(Score, DocAddress)], expected: &[(Score, DocAddress)]) {
|
||||
@@ -741,15 +737,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_collector_not_at_capacity_without_offset() -> crate::Result<()> {
|
||||
let index = make_index()?;
|
||||
fn test_top_collector_not_at_capacity_without_offset() {
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax")?;
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
let score_docs: Vec<(Score, DocAddress)> = index
|
||||
.reader()?
|
||||
.reader()
|
||||
.unwrap()
|
||||
.searcher()
|
||||
.search(&text_query, &TopDocs::with_limit(4))?;
|
||||
.search(&text_query, &TopDocs::with_limit(4))
|
||||
.unwrap();
|
||||
assert_results_equals(
|
||||
&score_docs,
|
||||
&[
|
||||
@@ -758,12 +756,11 @@ mod tests {
|
||||
(0.48527452, DocAddress::new(0, 0)),
|
||||
],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_top_collector_not_at_capacity_with_offset() {
|
||||
let index = make_index().unwrap();
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
@@ -778,7 +775,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_top_collector_at_capacity() {
|
||||
let index = make_index().unwrap();
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
@@ -799,7 +796,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_top_collector_at_capacity_with_offset() {
|
||||
let index = make_index().unwrap();
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
@@ -820,7 +817,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_top_collector_stable_sorting() {
|
||||
let index = make_index().unwrap();
|
||||
let index = make_index();
|
||||
|
||||
// using AllQuery to get a constant score
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
@@ -851,35 +848,29 @@ mod tests {
|
||||
const SIZE: &str = "size";
|
||||
|
||||
#[test]
|
||||
fn test_top_field_collector_not_at_capacity() -> crate::Result<()> {
|
||||
fn test_top_field_collector_not_at_capacity() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let title = schema_builder.add_text_field(TITLE, TEXT);
|
||||
let size = schema_builder.add_u64_field(SIZE, FAST);
|
||||
let schema = schema_builder.build();
|
||||
let (index, query) = index("beer", title, schema, |index_writer| {
|
||||
index_writer
|
||||
.add_document(doc!(
|
||||
title => "bottle of beer",
|
||||
size => 12u64,
|
||||
))
|
||||
.unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(
|
||||
title => "growler of beer",
|
||||
size => 64u64,
|
||||
))
|
||||
.unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(
|
||||
title => "pint of beer",
|
||||
size => 16u64,
|
||||
))
|
||||
.unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
title => "bottle of beer",
|
||||
size => 12u64,
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "growler of beer",
|
||||
size => 64u64,
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
title => "pint of beer",
|
||||
size => 16u64,
|
||||
));
|
||||
});
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
|
||||
let top_collector = TopDocs::with_limit(4).order_by_u64_field(size);
|
||||
let top_docs: Vec<(u64, DocAddress)> = searcher.search(&query, &top_collector)?;
|
||||
let top_docs: Vec<(u64, DocAddress)> = searcher.search(&query, &top_collector).unwrap();
|
||||
assert_eq!(
|
||||
&top_docs[..],
|
||||
&[
|
||||
@@ -888,7 +879,6 @@ mod tests {
|
||||
(12, DocAddress::new(0, 0))
|
||||
]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -904,12 +894,12 @@ mod tests {
|
||||
index_writer.add_document(doc!(
|
||||
name => "Paul Robeson",
|
||||
birthday => pr_birthday
|
||||
))?;
|
||||
));
|
||||
let mr_birthday = crate::DateTime::from_str("1947-11-08T00:00:00+00:00")?;
|
||||
index_writer.add_document(doc!(
|
||||
name => "Minnie Riperton",
|
||||
birthday => mr_birthday
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let top_collector = TopDocs::with_limit(3).order_by_fast_field(birthday);
|
||||
@@ -936,11 +926,11 @@ mod tests {
|
||||
index_writer.add_document(doc!(
|
||||
city => "georgetown",
|
||||
altitude => -1i64,
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
city => "tokyo",
|
||||
altitude => 40i64,
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let top_collector = TopDocs::with_limit(3).order_by_fast_field(altitude);
|
||||
@@ -966,11 +956,11 @@ mod tests {
|
||||
index_writer.add_document(doc!(
|
||||
city => "georgetown",
|
||||
altitude => -1.0f64,
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
city => "tokyo",
|
||||
altitude => 40f64,
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let top_collector = TopDocs::with_limit(3).order_by_fast_field(altitude);
|
||||
@@ -993,12 +983,10 @@ mod tests {
|
||||
let size = schema_builder.add_u64_field(SIZE, FAST);
|
||||
let schema = schema_builder.build();
|
||||
let (index, _) = index("beer", title, schema, |index_writer| {
|
||||
index_writer
|
||||
.add_document(doc!(
|
||||
title => "bottle of beer",
|
||||
size => 12u64,
|
||||
))
|
||||
.unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
title => "bottle of beer",
|
||||
size => 12u64,
|
||||
));
|
||||
});
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let top_collector = TopDocs::with_limit(4).order_by_u64_field(Field::from_field_id(2));
|
||||
@@ -1015,7 +1003,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(size=>1u64))?;
|
||||
index_writer.add_document(doc!(size=>1u64));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let segment = searcher.segment_reader(0);
|
||||
@@ -1032,7 +1020,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(size=>1u64))?;
|
||||
index_writer.add_document(doc!(size=>1u64));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let segment = searcher.segment_reader(0);
|
||||
@@ -1045,26 +1033,30 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_tweak_score_top_collector_with_offset() -> crate::Result<()> {
|
||||
let index = make_index()?;
|
||||
fn test_tweak_score_top_collector_with_offset() {
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax")?;
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
let collector = TopDocs::with_limit(2).and_offset(1).tweak_score(
|
||||
move |_segment_reader: &SegmentReader| move |doc: DocId, _original_score: Score| doc,
|
||||
);
|
||||
let score_docs: Vec<(u32, DocAddress)> =
|
||||
index.reader()?.searcher().search(&text_query, &collector)?;
|
||||
let score_docs: Vec<(u32, DocAddress)> = index
|
||||
.reader()
|
||||
.unwrap()
|
||||
.searcher()
|
||||
.search(&text_query, &collector)
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(
|
||||
score_docs,
|
||||
vec![(1, DocAddress::new(0, 1)), (0, DocAddress::new(0, 0)),]
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_custom_score_top_collector_with_offset() {
|
||||
let index = make_index().unwrap();
|
||||
let index = make_index();
|
||||
let field = index.schema().get_field("text").unwrap();
|
||||
let query_parser = QueryParser::for_index(&index, vec![field]);
|
||||
let text_query = query_parser.parse_query("droopy tax").unwrap();
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crossbeam::channel;
|
||||
use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
|
||||
/// Search executor whether search request are single thread or multithread.
|
||||
///
|
||||
@@ -11,8 +10,6 @@ use rayon::{ThreadPool, ThreadPoolBuilder};
|
||||
pub enum Executor {
|
||||
/// Single thread variant of an Executor
|
||||
SingleThread,
|
||||
/// Thread pool variant of an Executor
|
||||
ThreadPool(ThreadPool),
|
||||
}
|
||||
|
||||
impl Executor {
|
||||
@@ -21,15 +18,6 @@ impl Executor {
|
||||
Executor::SingleThread
|
||||
}
|
||||
|
||||
/// Creates an Executor that dispatches the tasks in a thread pool.
|
||||
pub fn multi_thread(num_threads: usize, prefix: &'static str) -> crate::Result<Executor> {
|
||||
let pool = ThreadPoolBuilder::new()
|
||||
.num_threads(num_threads)
|
||||
.thread_name(move |num| format!("{}{}", prefix, num))
|
||||
.build()?;
|
||||
Ok(Executor::ThreadPool(pool))
|
||||
}
|
||||
|
||||
/// Perform a map in the thread pool.
|
||||
///
|
||||
/// Regardless of the executor (`SingleThread` or `ThreadPool`), panics in the task
|
||||
@@ -46,40 +34,6 @@ impl Executor {
|
||||
) -> crate::Result<Vec<R>> {
|
||||
match self {
|
||||
Executor::SingleThread => args.map(f).collect::<crate::Result<_>>(),
|
||||
Executor::ThreadPool(pool) => {
|
||||
let args_with_indices: Vec<(usize, A)> = args.enumerate().collect();
|
||||
let num_fruits = args_with_indices.len();
|
||||
let fruit_receiver = {
|
||||
let (fruit_sender, fruit_receiver) = channel::unbounded();
|
||||
pool.scope(|scope| {
|
||||
for arg_with_idx in args_with_indices {
|
||||
scope.spawn(|_| {
|
||||
let (idx, arg) = arg_with_idx;
|
||||
let fruit = f(arg);
|
||||
if let Err(err) = fruit_sender.send((idx, fruit)) {
|
||||
error!("Failed to send search task. It probably means all search threads have panicked. {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
fruit_receiver
|
||||
// This ends the scope of fruit_sender.
|
||||
// This is important as it makes it possible for the fruit_receiver iteration to
|
||||
// terminate.
|
||||
};
|
||||
// This is lame, but safe.
|
||||
let mut results_with_position = Vec::with_capacity(num_fruits);
|
||||
for (pos, fruit_res) in fruit_receiver {
|
||||
let fruit = fruit_res?;
|
||||
results_with_position.push((pos, fruit));
|
||||
}
|
||||
results_with_position.sort_by_key(|(pos, _)| *pos);
|
||||
assert_eq!(results_with_position.len(), num_fruits);
|
||||
Ok(results_with_position
|
||||
.into_iter()
|
||||
.map(|(_, fruit)| fruit)
|
||||
.collect::<Vec<_>>())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,8 +123,8 @@ impl IndexBuilder {
|
||||
/// If a previous index was in this directory, it returns an `IndexAlreadyExists` error.
|
||||
#[cfg(feature = "mmap")]
|
||||
pub fn create_in_dir<P: AsRef<Path>>(self, directory_path: P) -> crate::Result<Index> {
|
||||
let mmap_directory: Box<dyn Directory> = Box::new(MmapDirectory::open(directory_path)?);
|
||||
if Index::exists(&*mmap_directory)? {
|
||||
let mmap_directory = MmapDirectory::open(directory_path)?;
|
||||
if Index::exists(&mmap_directory)? {
|
||||
return Err(TantivyError::IndexAlreadyExists);
|
||||
}
|
||||
self.create(mmap_directory)
|
||||
@@ -139,7 +139,7 @@ impl IndexBuilder {
|
||||
/// For other unit tests, prefer the `RAMDirectory`, see: `create_in_ram`.
|
||||
#[cfg(feature = "mmap")]
|
||||
pub fn create_from_tempdir(self) -> crate::Result<Index> {
|
||||
let mmap_directory: Box<dyn Directory> = Box::new(MmapDirectory::create_from_tempdir()?);
|
||||
let mmap_directory = MmapDirectory::create_from_tempdir()?;
|
||||
self.create(mmap_directory)
|
||||
}
|
||||
fn get_expect_schema(&self) -> crate::Result<Schema> {
|
||||
@@ -149,9 +149,8 @@ impl IndexBuilder {
|
||||
.ok_or(TantivyError::IndexBuilderMissingArgument("schema"))
|
||||
}
|
||||
/// Opens or creates a new index in the provided directory
|
||||
pub fn open_or_create<T: Into<Box<dyn Directory>>>(self, dir: T) -> crate::Result<Index> {
|
||||
let dir = dir.into();
|
||||
if !Index::exists(&*dir)? {
|
||||
pub fn open_or_create<Dir: Directory>(self, dir: Dir) -> crate::Result<Index> {
|
||||
if !Index::exists(&dir)? {
|
||||
return self.create(dir);
|
||||
}
|
||||
let index = Index::open(dir)?;
|
||||
@@ -166,8 +165,7 @@ impl IndexBuilder {
|
||||
/// Creates a new index given an implementation of the trait `Directory`.
|
||||
///
|
||||
/// If a directory previously existed, it will be erased.
|
||||
fn create<T: Into<Box<dyn Directory>>>(self, dir: T) -> crate::Result<Index> {
|
||||
let dir = dir.into();
|
||||
fn create<Dir: Directory>(self, dir: Dir) -> crate::Result<Index> {
|
||||
let directory = ManagedDirectory::wrap(dir)?;
|
||||
save_new_metas(
|
||||
self.get_expect_schema()?,
|
||||
@@ -200,7 +198,7 @@ impl Index {
|
||||
/// Examines the directory to see if it contains an index.
|
||||
///
|
||||
/// Effectively, it only checks for the presence of the `meta.json` file.
|
||||
pub fn exists(dir: &dyn Directory) -> Result<bool, OpenReadError> {
|
||||
pub fn exists<Dir: Directory>(dir: &Dir) -> Result<bool, OpenReadError> {
|
||||
dir.exists(&META_FILEPATH)
|
||||
}
|
||||
|
||||
@@ -252,11 +250,7 @@ impl Index {
|
||||
}
|
||||
|
||||
/// Opens or creates a new index in the provided directory
|
||||
pub fn open_or_create<T: Into<Box<dyn Directory>>>(
|
||||
dir: T,
|
||||
schema: Schema,
|
||||
) -> crate::Result<Index> {
|
||||
let dir = dir.into();
|
||||
pub fn open_or_create<Dir: Directory>(dir: Dir, schema: Schema) -> crate::Result<Index> {
|
||||
IndexBuilder::new().schema(schema).open_or_create(dir)
|
||||
}
|
||||
|
||||
@@ -276,12 +270,11 @@ impl Index {
|
||||
/// Creates a new index given an implementation of the trait `Directory`.
|
||||
///
|
||||
/// If a directory previously existed, it will be erased.
|
||||
pub fn create<T: Into<Box<dyn Directory>>>(
|
||||
dir: T,
|
||||
pub fn create<Dir: Directory>(
|
||||
dir: Dir,
|
||||
schema: Schema,
|
||||
settings: IndexSettings,
|
||||
) -> crate::Result<Index> {
|
||||
let dir: Box<dyn Directory> = dir.into();
|
||||
let mut builder = IndexBuilder::new().schema(schema);
|
||||
builder = builder.settings(settings);
|
||||
builder.create(dir)
|
||||
@@ -372,8 +365,7 @@ impl Index {
|
||||
}
|
||||
|
||||
/// Open the index using the provided directory
|
||||
pub fn open<T: Into<Box<dyn Directory>>>(directory: T) -> crate::Result<Index> {
|
||||
let directory = directory.into();
|
||||
pub fn open<D: Directory>(directory: D) -> crate::Result<Index> {
|
||||
let directory = ManagedDirectory::wrap(directory)?;
|
||||
let inventory = SegmentMetaInventory::default();
|
||||
let metas = load_metas(&directory, &inventory)?;
|
||||
@@ -403,7 +395,9 @@ impl Index {
|
||||
///
|
||||
/// # Errors
|
||||
/// If the lockfile already exists, returns `Error::DirectoryLockBusy` or an `Error::IoError`.
|
||||
/// If the heap size per thread is too small or too big, returns `TantivyError::InvalidArgument`
|
||||
///
|
||||
/// # Panics
|
||||
/// If the heap size per thread is too small, panics.
|
||||
pub fn writer_with_num_threads(
|
||||
&self,
|
||||
num_threads: usize,
|
||||
@@ -445,13 +439,14 @@ impl Index {
|
||||
/// Creates a multithreaded writer
|
||||
///
|
||||
/// Tantivy will automatically define the number of threads to use, but
|
||||
/// no more than 8 threads.
|
||||
/// no more than [`MAX_NUM_THREAD`] threads.
|
||||
/// `overall_heap_size_in_bytes` is the total target memory usage that will be split
|
||||
/// between a given number of threads.
|
||||
///
|
||||
/// # Errors
|
||||
/// If the lockfile already exists, returns `Error::FileAlreadyExists`.
|
||||
/// If the heap size per thread is too small or too big, returns `TantivyError::InvalidArgument`
|
||||
/// # Panics
|
||||
/// If the heap size per thread is too small, panics.
|
||||
pub fn writer(&self, overall_heap_size_in_bytes: usize) -> crate::Result<IndexWriter> {
|
||||
let mut num_threads = std::cmp::min(num_cpus::get(), MAX_NUM_THREAD);
|
||||
let heap_size_in_bytes_per_thread = overall_heap_size_in_bytes / num_threads;
|
||||
@@ -582,15 +577,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_index_exists() {
|
||||
let directory: Box<dyn Directory> = Box::new(RamDirectory::create());
|
||||
assert!(!Index::exists(directory.as_ref()).unwrap());
|
||||
let directory = RamDirectory::create();
|
||||
assert!(!Index::exists(&directory).unwrap());
|
||||
assert!(Index::create(
|
||||
directory.clone(),
|
||||
throw_away_schema(),
|
||||
IndexSettings::default()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Index::exists(directory.as_ref()).unwrap());
|
||||
assert!(Index::exists(&directory).unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -603,27 +598,27 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn open_or_create_should_open() {
|
||||
let directory: Box<dyn Directory> = Box::new(RamDirectory::create());
|
||||
let directory = RamDirectory::create();
|
||||
assert!(Index::create(
|
||||
directory.clone(),
|
||||
throw_away_schema(),
|
||||
IndexSettings::default()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Index::exists(directory.as_ref()).unwrap());
|
||||
assert!(Index::exists(&directory).unwrap());
|
||||
assert!(Index::open_or_create(directory, throw_away_schema()).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_should_wipeoff_existing() {
|
||||
let directory: Box<dyn Directory> = Box::new(RamDirectory::create());
|
||||
let directory = RamDirectory::create();
|
||||
assert!(Index::create(
|
||||
directory.clone(),
|
||||
throw_away_schema(),
|
||||
IndexSettings::default()
|
||||
)
|
||||
.is_ok());
|
||||
assert!(Index::exists(directory.as_ref()).unwrap());
|
||||
assert!(Index::exists(&directory).unwrap());
|
||||
assert!(Index::create(
|
||||
directory,
|
||||
Schema::builder().build(),
|
||||
@@ -657,7 +652,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_on_commit_reload_policy() -> crate::Result<()> {
|
||||
fn test_index_on_commit_reload_policy() {
|
||||
let schema = throw_away_schema();
|
||||
let field = schema.get_field("num_likes").unwrap();
|
||||
let index = Index::create_in_ram(schema);
|
||||
@@ -667,7 +662,7 @@ mod tests {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
test_index_on_commit_reload_policy_aux(field, &index, &reader)
|
||||
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||
}
|
||||
|
||||
#[cfg(feature = "mmap")]
|
||||
@@ -679,7 +674,7 @@ mod tests {
|
||||
use tempfile::TempDir;
|
||||
|
||||
#[test]
|
||||
fn test_index_on_commit_reload_policy_mmap() -> crate::Result<()> {
|
||||
fn test_index_on_commit_reload_policy_mmap() {
|
||||
let schema = throw_away_schema();
|
||||
let field = schema.get_field("num_likes").unwrap();
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
@@ -691,7 +686,7 @@ mod tests {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
test_index_on_commit_reload_policy_aux(field, &index, &reader)
|
||||
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -706,7 +701,7 @@ mod tests {
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?;
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
writer.add_document(doc!(field=>1u64))?;
|
||||
writer.add_document(doc!(field=>1u64));
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
let _handle = index.directory_mut().watch(WatchCallback::new(move || {
|
||||
let _ = sender.send(());
|
||||
@@ -720,7 +715,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_on_commit_reload_policy_different_directories() -> crate::Result<()> {
|
||||
fn test_index_on_commit_reload_policy_different_directories() {
|
||||
let schema = throw_away_schema();
|
||||
let field = schema.get_field("num_likes").unwrap();
|
||||
let tempdir = TempDir::new().unwrap();
|
||||
@@ -733,14 +728,10 @@ mod tests {
|
||||
.try_into()
|
||||
.unwrap();
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
test_index_on_commit_reload_policy_aux(field, &write_index, &reader)
|
||||
test_index_on_commit_reload_policy_aux(field, &write_index, &reader);
|
||||
}
|
||||
}
|
||||
fn test_index_on_commit_reload_policy_aux(
|
||||
field: Field,
|
||||
index: &Index,
|
||||
reader: &IndexReader,
|
||||
) -> crate::Result<()> {
|
||||
fn test_index_on_commit_reload_policy_aux(field: Field, index: &Index, reader: &IndexReader) {
|
||||
let mut reader_index = reader.index();
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
let _watch_handle = reader_index
|
||||
@@ -748,9 +739,9 @@ mod tests {
|
||||
.watch(WatchCallback::new(move || {
|
||||
let _ = sender.send(());
|
||||
}));
|
||||
let mut writer = index.writer_for_tests()?;
|
||||
let mut writer = index.writer_for_tests().unwrap();
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
writer.add_document(doc!(field=>1u64))?;
|
||||
writer.add_document(doc!(field=>1u64));
|
||||
writer.commit().unwrap();
|
||||
// We need a loop here because it is possible for notify to send more than
|
||||
// one modify event. It was observed on CI on MacOS.
|
||||
@@ -760,7 +751,7 @@ mod tests {
|
||||
break;
|
||||
}
|
||||
}
|
||||
writer.add_document(doc!(field=>2u64))?;
|
||||
writer.add_document(doc!(field=>2u64));
|
||||
writer.commit().unwrap();
|
||||
// ... Same as above
|
||||
loop {
|
||||
@@ -769,37 +760,37 @@ mod tests {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// This test will not pass on windows, because windows
|
||||
// prevent deleting files that are MMapped.
|
||||
#[cfg(not(target_os = "windows"))]
|
||||
#[test]
|
||||
fn garbage_collect_works_as_intended() -> crate::Result<()> {
|
||||
fn garbage_collect_works_as_intended() {
|
||||
let directory = RamDirectory::create();
|
||||
let schema = throw_away_schema();
|
||||
let field = schema.get_field("num_likes").unwrap();
|
||||
let index = Index::create(directory.clone(), schema, IndexSettings::default())?;
|
||||
let index = Index::create(directory.clone(), schema, IndexSettings::default()).unwrap();
|
||||
|
||||
let mut writer = index.writer_with_num_threads(8, 24_000_000).unwrap();
|
||||
for i in 0u64..8_000u64 {
|
||||
writer.add_document(doc!(field => i))?;
|
||||
writer.add_document(doc!(field => i));
|
||||
}
|
||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||
let _handle = directory.watch(WatchCallback::new(move || {
|
||||
let _ = sender.send(());
|
||||
}));
|
||||
writer.commit()?;
|
||||
writer.commit().unwrap();
|
||||
let mem_right_after_commit = directory.total_mem_usage();
|
||||
assert!(receiver.recv().is_ok());
|
||||
let reader = index
|
||||
.reader_builder()
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?;
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(reader.searcher().num_docs(), 8_000);
|
||||
writer.wait_merging_threads()?;
|
||||
writer.wait_merging_threads().unwrap();
|
||||
let mem_right_after_merge_finished = directory.total_mem_usage();
|
||||
|
||||
reader.reload().unwrap();
|
||||
@@ -811,6 +802,5 @@ mod tests {
|
||||
mem_right_after_merge_finished,
|
||||
mem_right_after_commit
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ impl Searcher {
|
||||
&self.segment_readers
|
||||
}
|
||||
|
||||
/// Returns the segment_reader associated with the given segment_ord
|
||||
/// Returns the segment_reader associated with the given segment_ordinal
|
||||
pub fn segment_reader(&self, segment_ord: u32) -> &SegmentReader {
|
||||
&self.segment_readers[segment_ord as usize]
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@ use crate::core::SegmentId;
|
||||
use crate::directory::CompositeFile;
|
||||
use crate::directory::FileSlice;
|
||||
use crate::error::DataCorruption;
|
||||
use crate::fastfield::intersect_alive_bitsets;
|
||||
use crate::fastfield::AliveBitSet;
|
||||
use crate::fastfield::FacetReader;
|
||||
use crate::fastfield::FastFieldReaders;
|
||||
@@ -141,14 +140,6 @@ impl SegmentReader {
|
||||
|
||||
/// Open a new segment for reading.
|
||||
pub fn open(segment: &Segment) -> crate::Result<SegmentReader> {
|
||||
Self::open_with_custom_alive_set(segment, None)
|
||||
}
|
||||
|
||||
/// Open a new segment for reading.
|
||||
pub fn open_with_custom_alive_set(
|
||||
segment: &Segment,
|
||||
custom_bitset: Option<AliveBitSet>,
|
||||
) -> crate::Result<SegmentReader> {
|
||||
let termdict_file = segment.open_read(SegmentComponent::Terms)?;
|
||||
let termdict_composite = CompositeFile::open(&termdict_file)?;
|
||||
|
||||
@@ -173,29 +164,22 @@ impl SegmentReader {
|
||||
let fast_fields_composite = CompositeFile::open(&fast_fields_data)?;
|
||||
let fast_field_readers =
|
||||
Arc::new(FastFieldReaders::new(schema.clone(), fast_fields_composite));
|
||||
|
||||
let fieldnorm_data = segment.open_read(SegmentComponent::FieldNorms)?;
|
||||
let fieldnorm_readers = FieldNormReaders::open(fieldnorm_data)?;
|
||||
|
||||
let original_bitset = if segment.meta().has_deletes() {
|
||||
let delete_file_slice = segment.open_read(SegmentComponent::Delete)?;
|
||||
let delete_data = delete_file_slice.read_bytes()?;
|
||||
Some(AliveBitSet::open(delete_data))
|
||||
let alive_bitset_opt = if segment.meta().has_deletes() {
|
||||
let alive_bitset_bytes = segment.open_read(SegmentComponent::Delete)?.read_bytes()?;
|
||||
let alive_bitset = AliveBitSet::open(alive_bitset_bytes);
|
||||
Some(alive_bitset)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let alive_bitset_opt = intersect_alive_bitset(original_bitset, custom_bitset);
|
||||
|
||||
let max_doc = segment.meta().max_doc();
|
||||
let num_docs = alive_bitset_opt
|
||||
.as_ref()
|
||||
.map(|alive_bitset| alive_bitset.num_alive_docs() as u32)
|
||||
.unwrap_or(max_doc);
|
||||
|
||||
Ok(SegmentReader {
|
||||
inv_idx_reader_cache: Default::default(),
|
||||
num_docs,
|
||||
max_doc,
|
||||
max_doc: segment.meta().max_doc(),
|
||||
num_docs: segment.meta().num_docs(),
|
||||
termdict_composite,
|
||||
postings_composite,
|
||||
fast_fields_readers: fast_field_readers,
|
||||
@@ -327,21 +311,6 @@ impl SegmentReader {
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_alive_bitset(
|
||||
left_opt: Option<AliveBitSet>,
|
||||
right_opt: Option<AliveBitSet>,
|
||||
) -> Option<AliveBitSet> {
|
||||
match (left_opt, right_opt) {
|
||||
(Some(left), Some(right)) => {
|
||||
assert_eq!(left.bitset().max_value(), right.bitset().max_value());
|
||||
Some(intersect_alive_bitsets(left, right))
|
||||
}
|
||||
(Some(left), None) => Some(left),
|
||||
(None, Some(right)) => Some(right),
|
||||
(None, None) => None,
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for SegmentReader {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "SegmentReader({:?})", self.segment_id)
|
||||
@@ -364,10 +333,10 @@ mod test {
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => "tantivy"))?;
|
||||
index_writer.add_document(doc!(name => "horse"))?;
|
||||
index_writer.add_document(doc!(name => "jockey"))?;
|
||||
index_writer.add_document(doc!(name => "cap"))?;
|
||||
index_writer.add_document(doc!(name => "tantivy"));
|
||||
index_writer.add_document(doc!(name => "horse"));
|
||||
index_writer.add_document(doc!(name => "jockey"));
|
||||
index_writer.add_document(doc!(name => "cap"));
|
||||
// we should now have one segment with two docs
|
||||
index_writer.delete_term(Term::from_field_text(name, "horse"));
|
||||
index_writer.delete_term(Term::from_field_text(name, "cap"));
|
||||
@@ -390,10 +359,10 @@ mod test {
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => "tantivy"))?;
|
||||
index_writer.add_document(doc!(name => "horse"))?;
|
||||
index_writer.add_document(doc!(name => "jockey"))?;
|
||||
index_writer.add_document(doc!(name => "cap"))?;
|
||||
index_writer.add_document(doc!(name => "tantivy"));
|
||||
index_writer.add_document(doc!(name => "horse"));
|
||||
index_writer.add_document(doc!(name => "jockey"));
|
||||
index_writer.add_document(doc!(name => "cap"));
|
||||
// we should now have one segment with two docs
|
||||
index_writer.commit()?;
|
||||
}
|
||||
|
||||
@@ -230,15 +230,3 @@ where
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Box<dyn Directory> {
|
||||
fn clone(&self) -> Self {
|
||||
self.box_clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Directory + 'static> From<T> for Box<dyn Directory> {
|
||||
fn from(t: T) -> Self {
|
||||
Box::new(t)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,7 +64,7 @@ fn save_managed_paths(
|
||||
|
||||
impl ManagedDirectory {
|
||||
/// Wraps a directory as managed directory.
|
||||
pub fn wrap(directory: Box<dyn Directory>) -> crate::Result<ManagedDirectory> {
|
||||
pub fn wrap<Dir: Directory>(directory: Dir) -> crate::Result<ManagedDirectory> {
|
||||
match directory.atomic_read(&MANAGED_FILEPATH) {
|
||||
Ok(data) => {
|
||||
let managed_files_json = String::from_utf8_lossy(&data);
|
||||
@@ -76,14 +76,14 @@ impl ManagedDirectory {
|
||||
)
|
||||
})?;
|
||||
Ok(ManagedDirectory {
|
||||
directory,
|
||||
directory: Box::new(directory),
|
||||
meta_informations: Arc::new(RwLock::new(MetaInformation {
|
||||
managed_paths: managed_files,
|
||||
})),
|
||||
})
|
||||
}
|
||||
Err(OpenReadError::FileDoesNotExist(_)) => Ok(ManagedDirectory {
|
||||
directory,
|
||||
directory: Box::new(directory),
|
||||
meta_informations: Arc::default(),
|
||||
}),
|
||||
io_err @ Err(OpenReadError::IoError { .. }) => Err(io_err.err().unwrap().into()),
|
||||
@@ -340,7 +340,7 @@ mod tests_mmap_specific {
|
||||
let test_path2: &'static Path = Path::new("some_path_for_test_2");
|
||||
{
|
||||
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||
let mut managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory)).unwrap();
|
||||
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
||||
let write_file = managed_directory.open_write(test_path1).unwrap();
|
||||
write_file.terminate().unwrap();
|
||||
managed_directory
|
||||
@@ -355,7 +355,7 @@ mod tests_mmap_specific {
|
||||
}
|
||||
{
|
||||
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||
let mut managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory)).unwrap();
|
||||
let mut managed_directory = ManagedDirectory::wrap(mmap_directory).unwrap();
|
||||
assert!(managed_directory.exists(test_path1).unwrap());
|
||||
assert!(!managed_directory.exists(test_path2).unwrap());
|
||||
let living_files: HashSet<PathBuf> = HashSet::new();
|
||||
@@ -374,7 +374,7 @@ mod tests_mmap_specific {
|
||||
let living_files = HashSet::new();
|
||||
|
||||
let mmap_directory = MmapDirectory::open(&tempdir_path).unwrap();
|
||||
let mut managed_directory = ManagedDirectory::wrap(Box::new(mmap_directory)).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();
|
||||
|
||||
@@ -74,12 +74,20 @@ pub struct CacheInfo {
|
||||
pub mmapped: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MmapCache {
|
||||
counters: CacheCounters,
|
||||
cache: HashMap<PathBuf, WeakArcBytes>,
|
||||
}
|
||||
|
||||
impl Default for MmapCache {
|
||||
fn default() -> MmapCache {
|
||||
MmapCache {
|
||||
counters: CacheCounters::default(),
|
||||
cache: HashMap::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MmapCache {
|
||||
fn get_info(&self) -> CacheInfo {
|
||||
let paths: Vec<PathBuf> = self.cache.keys().cloned().collect();
|
||||
@@ -234,7 +242,7 @@ impl MmapDirectory {
|
||||
}
|
||||
|
||||
let fd = open_opts.open(&self.inner.root_path)?;
|
||||
fd.sync_data()?;
|
||||
fd.sync_all()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -288,7 +296,8 @@ impl Write for SafeFileWriter {
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
self.0.flush()?;
|
||||
self.0.sync_all()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -300,9 +309,7 @@ impl Seek for SafeFileWriter {
|
||||
|
||||
impl TerminatingWrite for SafeFileWriter {
|
||||
fn terminate_ref(&mut self, _: AntiCallToken) -> io::Result<()> {
|
||||
self.0.flush()?;
|
||||
self.0.sync_data()?;
|
||||
Ok(())
|
||||
self.flush()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -332,7 +339,6 @@ pub(crate) fn atomic_write(path: &Path, content: &[u8]) -> io::Result<()> {
|
||||
let mut tempfile = tempfile::Builder::new().tempfile_in(&parent_path)?;
|
||||
tempfile.write_all(content)?;
|
||||
tempfile.flush()?;
|
||||
tempfile.as_file_mut().sync_data()?;
|
||||
tempfile.into_temp_path().persist(path)?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -576,8 +582,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_mmap_released() -> crate::Result<()> {
|
||||
let mmap_directory = MmapDirectory::create_from_tempdir()?;
|
||||
fn test_mmap_released() {
|
||||
let mmap_directory = MmapDirectory::create_from_tempdir().unwrap();
|
||||
let mut schema_builder: SchemaBuilder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
@@ -586,30 +592,31 @@ mod tests {
|
||||
let index =
|
||||
Index::create(mmap_directory.clone(), schema, IndexSettings::default()).unwrap();
|
||||
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let mut log_merge_policy = LogMergePolicy::default();
|
||||
log_merge_policy.set_min_num_segments(3);
|
||||
index_writer.set_merge_policy(Box::new(log_merge_policy));
|
||||
for _num_commits in 0..10 {
|
||||
for _ in 0..10 {
|
||||
index_writer.add_document(doc!(text_field=>"abc"))?;
|
||||
index_writer.add_document(doc!(text_field=>"abc"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
|
||||
let reader = index
|
||||
.reader_builder()
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?;
|
||||
.try_into()
|
||||
.unwrap();
|
||||
|
||||
for _ in 0..4 {
|
||||
index_writer.add_document(doc!(text_field=>"abc"))?;
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
index_writer.add_document(doc!(text_field=>"abc"));
|
||||
index_writer.commit().unwrap();
|
||||
reader.reload().unwrap();
|
||||
}
|
||||
index_writer.wait_merging_threads()?;
|
||||
index_writer.wait_merging_threads().unwrap();
|
||||
|
||||
reader.reload()?;
|
||||
reader.reload().unwrap();
|
||||
let num_segments = reader.searcher().segment_readers().len();
|
||||
assert!(num_segments <= 4);
|
||||
let num_components_except_deletes_and_tempstore =
|
||||
@@ -620,6 +627,5 @@ mod tests {
|
||||
);
|
||||
}
|
||||
assert!(mmap_directory.get_cache_info().mmapped.is_empty());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,6 +18,13 @@ use super::FileHandle;
|
||||
/// Writer associated with the `RamDirectory`
|
||||
///
|
||||
/// The Writer just writes a buffer.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// On drop, if the writer was left in a *dirty* state.
|
||||
/// That is, if flush was not called after the last call
|
||||
/// to write.
|
||||
///
|
||||
struct VecWriter {
|
||||
path: PathBuf,
|
||||
shared_directory: RamDirectory,
|
||||
@@ -39,7 +46,7 @@ impl VecWriter {
|
||||
impl Drop for VecWriter {
|
||||
fn drop(&mut self) {
|
||||
if !self.is_flushed {
|
||||
warn!(
|
||||
panic!(
|
||||
"You forgot to flush {:?} before its writter got Drop. Do not rely on drop. This also occurs when the indexer crashed, so you may want to check the logs for the root cause.",
|
||||
self.path
|
||||
)
|
||||
@@ -214,8 +221,14 @@ impl Directory for RamDirectory {
|
||||
}
|
||||
|
||||
fn atomic_write(&self, path: &Path, data: &[u8]) -> io::Result<()> {
|
||||
fail_point!("RamDirectory::atomic_write", |msg| Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
msg.unwrap_or_else(|| "Undefined".to_string())
|
||||
)));
|
||||
let path_buf = PathBuf::from(path);
|
||||
|
||||
self.fs.write().unwrap().write(path_buf, data);
|
||||
|
||||
if path == *META_FILEPATH {
|
||||
let _ = self.fs.write().unwrap().watch_router.broadcast();
|
||||
}
|
||||
|
||||
@@ -118,6 +118,15 @@ mod ram_directory_tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn ram_directory_panics_if_flush_forgotten() {
|
||||
let test_path: &'static Path = Path::new("some_path_for_test");
|
||||
let ram_directory = RamDirectory::create();
|
||||
let mut write_file = ram_directory.open_write(test_path).unwrap();
|
||||
assert!(write_file.write_all(&[4]).is_ok());
|
||||
}
|
||||
|
||||
fn test_simple(directory: &dyn Directory) -> crate::Result<()> {
|
||||
let test_path: &'static Path = Path::new("some_path_for_test");
|
||||
let mut write_file = directory.open_write(test_path)?;
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use crate::space_usage::ByteCount;
|
||||
use crate::DocId;
|
||||
use common::intersect_bitsets;
|
||||
use common::BitSet;
|
||||
use common::ReadOnlyBitSet;
|
||||
use common::ReadSerializedBitSet;
|
||||
use ownedbytes::OwnedBytes;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
@@ -21,19 +20,8 @@ pub fn write_alive_bitset<T: Write>(alive_bitset: &BitSet, writer: &mut T) -> io
|
||||
#[derive(Clone)]
|
||||
pub struct AliveBitSet {
|
||||
num_alive_docs: usize,
|
||||
bitset: ReadOnlyBitSet,
|
||||
}
|
||||
|
||||
/// Intersects two AliveBitSets in a new one.
|
||||
/// The two bitsets need to have the same max_value.
|
||||
pub fn intersect_alive_bitsets(left: AliveBitSet, right: AliveBitSet) -> AliveBitSet {
|
||||
assert_eq!(left.bitset().max_value(), right.bitset().max_value());
|
||||
let bitset = intersect_bitsets(left.bitset(), right.bitset());
|
||||
let num_alive_docs = bitset.len();
|
||||
AliveBitSet {
|
||||
num_alive_docs,
|
||||
bitset,
|
||||
}
|
||||
bitset: ReadSerializedBitSet,
|
||||
num_bytes: ByteCount,
|
||||
}
|
||||
|
||||
impl AliveBitSet {
|
||||
@@ -50,15 +38,15 @@ impl AliveBitSet {
|
||||
Self::open(alive_bitset_bytes)
|
||||
}
|
||||
|
||||
pub(crate) fn from_bitset(bitset: &BitSet) -> AliveBitSet {
|
||||
let readonly_bitset = ReadOnlyBitSet::from(bitset);
|
||||
AliveBitSet::from(readonly_bitset)
|
||||
}
|
||||
|
||||
/// Opens a delete bitset given its file.
|
||||
pub fn open(bytes: OwnedBytes) -> AliveBitSet {
|
||||
let bitset = ReadOnlyBitSet::open(bytes);
|
||||
AliveBitSet::from(bitset)
|
||||
let num_bytes = bytes.len();
|
||||
let bitset = ReadSerializedBitSet::open(bytes);
|
||||
AliveBitSet {
|
||||
num_alive_docs: bitset.len(),
|
||||
bitset,
|
||||
num_bytes,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true iff the document is still "alive". In other words, if it has not been deleted.
|
||||
@@ -73,7 +61,7 @@ impl AliveBitSet {
|
||||
!self.is_alive(doc)
|
||||
}
|
||||
|
||||
/// Iterate over the alive doc_ids.
|
||||
/// Iterate over the alive docids.
|
||||
#[inline]
|
||||
pub fn iter_alive(&self) -> impl Iterator<Item = DocId> + '_ {
|
||||
self.bitset.iter()
|
||||
@@ -81,7 +69,7 @@ impl AliveBitSet {
|
||||
|
||||
/// Get underlying bitset
|
||||
#[inline]
|
||||
pub fn bitset(&self) -> &ReadOnlyBitSet {
|
||||
pub fn bitset(&self) -> &ReadSerializedBitSet {
|
||||
&self.bitset
|
||||
}
|
||||
|
||||
@@ -92,17 +80,7 @@ impl AliveBitSet {
|
||||
|
||||
/// Summarize total space usage of this bitset.
|
||||
pub fn space_usage(&self) -> ByteCount {
|
||||
self.bitset().num_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ReadOnlyBitSet> for AliveBitSet {
|
||||
fn from(bitset: ReadOnlyBitSet) -> AliveBitSet {
|
||||
let num_alive_docs = bitset.len();
|
||||
AliveBitSet {
|
||||
num_alive_docs,
|
||||
bitset,
|
||||
}
|
||||
self.num_bytes
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,11 +18,11 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![0u8, 1, 2, 3]))?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![]))?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![255u8]))?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![1u8, 3, 5, 7, 9]))?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![0u8; 1000]))?;
|
||||
index_writer.add_document(doc!(bytes_field=>vec![0u8, 1, 2, 3]));
|
||||
index_writer.add_document(doc!(bytes_field=>vec![]));
|
||||
index_writer.add_document(doc!(bytes_field=>vec![255u8]));
|
||||
index_writer.add_document(doc!(bytes_field=>vec![1u8, 3, 5, 7, 9]));
|
||||
index_writer.add_document(doc!(bytes_field=>vec![0u8; 1000]));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
@@ -47,7 +47,7 @@ mod tests {
|
||||
index_writer.add_document(doc!(
|
||||
field => b"tantivy".as_ref(),
|
||||
field => b"lucene".as_ref()
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
Ok(index.reader()?.searcher())
|
||||
}
|
||||
|
||||
@@ -95,7 +95,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()))?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
@@ -118,7 +118,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()))?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
@@ -141,7 +141,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()))?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
@@ -164,7 +164,7 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()))?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
@@ -187,8 +187,8 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()))?;
|
||||
index_writer.add_document(Document::default())?;
|
||||
index_writer.add_document(doc!(facet_field=>Facet::from_text("/a/b").unwrap()));
|
||||
index_writer.add_document(Document::default());
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
@@ -210,8 +210,8 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(Document::default())?;
|
||||
index_writer.add_document(Document::default())?;
|
||||
index_writer.add_document(Document::default());
|
||||
index_writer.add_document(Document::default());
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let facet_reader = searcher
|
||||
|
||||
@@ -23,7 +23,6 @@ values stored.
|
||||
Read access performance is comparable to that of an array lookup.
|
||||
*/
|
||||
|
||||
pub use self::alive_bitset::intersect_alive_bitsets;
|
||||
pub use self::alive_bitset::write_alive_bitset;
|
||||
pub use self::alive_bitset::AliveBitSet;
|
||||
pub use self::bytes::{BytesFastFieldReader, BytesFastFieldWriter};
|
||||
@@ -497,18 +496,18 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_missing_date_fast_field() -> crate::Result<()> {
|
||||
fn test_merge_missing_date_fast_field() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let date_field = schema_builder.add_date_field("date", FAST);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
index_writer.add_document(doc!(date_field =>crate::chrono::prelude::Utc::now()))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!())?;
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
index_writer.add_document(doc!(date_field =>crate::chrono::prelude::Utc::now()));
|
||||
index_writer.commit().unwrap();
|
||||
index_writer.add_document(doc!());
|
||||
index_writer.commit().unwrap();
|
||||
let reader = index.reader().unwrap();
|
||||
let segment_ids: Vec<SegmentId> = reader
|
||||
.searcher()
|
||||
.segment_readers()
|
||||
@@ -517,10 +516,10 @@ mod tests {
|
||||
.collect();
|
||||
assert_eq!(segment_ids.len(), 2);
|
||||
let merge_future = index_writer.merge(&segment_ids[..]);
|
||||
futures::executor::block_on(merge_future)?;
|
||||
reader.reload()?;
|
||||
let merge_res = futures::executor::block_on(merge_future);
|
||||
assert!(merge_res.is_ok());
|
||||
assert!(reader.reload().is_ok());
|
||||
assert_eq!(reader.searcher().segment_readers().len(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -529,7 +528,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_datefastfield() -> crate::Result<()> {
|
||||
fn test_datefastfield() {
|
||||
use crate::fastfield::FastValue;
|
||||
let mut schema_builder = Schema::builder();
|
||||
let date_field = schema_builder.add_date_field("date", FAST);
|
||||
@@ -539,22 +538,22 @@ mod tests {
|
||||
);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
index_writer.add_document(doc!(
|
||||
date_field => crate::DateTime::from_u64(1i64.to_u64()),
|
||||
multi_date_field => crate::DateTime::from_u64(2i64.to_u64()),
|
||||
multi_date_field => crate::DateTime::from_u64(3i64.to_u64())
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
date_field => crate::DateTime::from_u64(4i64.to_u64())
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
multi_date_field => crate::DateTime::from_u64(5i64.to_u64()),
|
||||
multi_date_field => crate::DateTime::from_u64(6i64.to_u64())
|
||||
))?;
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
));
|
||||
index_writer.commit().unwrap();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
assert_eq!(searcher.segment_readers().len(), 1);
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
@@ -581,7 +580,6 @@ mod tests {
|
||||
assert_eq!(dates[0].timestamp(), 5i64);
|
||||
assert_eq!(dates[1].timestamp(), 6i64);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ mod tests {
|
||||
use test_env_log::test;
|
||||
|
||||
#[test]
|
||||
fn test_multivalued_u64() -> crate::Result<()> {
|
||||
fn test_multivalued_u64() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field = schema_builder.add_u64_field(
|
||||
"multifield",
|
||||
@@ -34,17 +34,17 @@ mod tests {
|
||||
);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(field=>1u64, field=>3u64))?;
|
||||
index_writer.add_document(doc!())?;
|
||||
index_writer.add_document(doc!(field=>4u64))?;
|
||||
index_writer.add_document(doc!(field=>5u64, field=>20u64,field=>1u64))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(field=>1u64, field=>3u64));
|
||||
index_writer.add_document(doc!());
|
||||
index_writer.add_document(doc!(field=>4u64));
|
||||
index_writer.add_document(doc!(field=>5u64, field=>20u64,field=>1u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let mut vals = Vec::new();
|
||||
let multi_value_reader = segment_reader.fast_fields().u64s(field)?;
|
||||
let multi_value_reader = segment_reader.fast_fields().u64s(field).unwrap();
|
||||
{
|
||||
multi_value_reader.get_vals(2, &mut vals);
|
||||
assert_eq!(&vals, &[4u64]);
|
||||
@@ -57,11 +57,10 @@ mod tests {
|
||||
multi_value_reader.get_vals(1, &mut vals);
|
||||
assert!(vals.is_empty());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multivalued_date() -> crate::Result<()> {
|
||||
fn test_multivalued_date() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let date_field = schema_builder.add_date_field(
|
||||
"multi_date_field",
|
||||
@@ -74,37 +73,40 @@ mod tests {
|
||||
schema_builder.add_i64_field("time_stamp_i", IntOptions::default().set_stored());
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let first_time_stamp = chrono::Utc::now();
|
||||
index_writer.add_document(
|
||||
doc!(date_field=>first_time_stamp, date_field=>first_time_stamp, time_i=>1i64),
|
||||
)?;
|
||||
index_writer.add_document(doc!(time_i=>0i64))?;
|
||||
);
|
||||
index_writer.add_document(doc!(time_i=>0i64));
|
||||
// add one second
|
||||
index_writer.add_document(
|
||||
doc!(date_field=>first_time_stamp + Duration::seconds(1), time_i=>2i64),
|
||||
)?;
|
||||
index_writer
|
||||
.add_document(doc!(date_field=>first_time_stamp + Duration::seconds(1), time_i=>2i64));
|
||||
// add another second
|
||||
let two_secs_ahead = first_time_stamp + Duration::seconds(2);
|
||||
index_writer.add_document(doc!(date_field=>two_secs_ahead, date_field=>two_secs_ahead,date_field=>two_secs_ahead, time_i=>3i64))?;
|
||||
index_writer.add_document(doc!(date_field=>two_secs_ahead, date_field=>two_secs_ahead,date_field=>two_secs_ahead, time_i=>3i64));
|
||||
// add three seconds
|
||||
index_writer.add_document(
|
||||
doc!(date_field=>first_time_stamp + Duration::seconds(3), time_i=>4i64),
|
||||
)?;
|
||||
index_writer.commit()?;
|
||||
index_writer
|
||||
.add_document(doc!(date_field=>first_time_stamp + Duration::seconds(3), time_i=>4i64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let reader = searcher.segment_reader(0);
|
||||
assert_eq!(reader.num_docs(), 5);
|
||||
|
||||
{
|
||||
let parser = QueryParser::for_index(&index, vec![date_field]);
|
||||
let query = parser.parse_query(&format!("\"{}\"", first_time_stamp.to_rfc3339()))?;
|
||||
let results = searcher.search(&query, &TopDocs::with_limit(5))?;
|
||||
let query = parser
|
||||
.parse_query(&format!("\"{}\"", first_time_stamp.to_rfc3339()))
|
||||
.expect("could not parse query");
|
||||
let results = searcher
|
||||
.search(&query, &TopDocs::with_limit(5))
|
||||
.expect("could not query index");
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
for (_score, doc_address) in results {
|
||||
let retrieved_doc = searcher.doc(doc_address)?;
|
||||
let retrieved_doc = searcher.doc(doc_address).expect("cannot fetch doc");
|
||||
assert_eq!(
|
||||
retrieved_doc
|
||||
.get_first(date_field)
|
||||
@@ -126,8 +128,12 @@ mod tests {
|
||||
|
||||
{
|
||||
let parser = QueryParser::for_index(&index, vec![date_field]);
|
||||
let query = parser.parse_query(&format!("\"{}\"", two_secs_ahead.to_rfc3339()))?;
|
||||
let results = searcher.search(&query, &TopDocs::with_limit(5))?;
|
||||
let query = parser
|
||||
.parse_query(&format!("\"{}\"", two_secs_ahead.to_rfc3339()))
|
||||
.expect("could not parse query");
|
||||
let results = searcher
|
||||
.search(&query, &TopDocs::with_limit(5))
|
||||
.expect("could not query index");
|
||||
|
||||
assert_eq!(results.len(), 1);
|
||||
|
||||
@@ -159,8 +165,10 @@ mod tests {
|
||||
(first_time_stamp + Duration::seconds(1)).to_rfc3339(),
|
||||
(first_time_stamp + Duration::seconds(3)).to_rfc3339()
|
||||
);
|
||||
let query = parser.parse_query(&range_q)?;
|
||||
let results = searcher.search(&query, &TopDocs::with_limit(5))?;
|
||||
let query = parser.parse_query(&range_q).expect("could not parse query");
|
||||
let results = searcher
|
||||
.search(&query, &TopDocs::with_limit(5))
|
||||
.expect("could not query index");
|
||||
|
||||
assert_eq!(results.len(), 2);
|
||||
for (i, doc_pair) in results.iter().enumerate() {
|
||||
@@ -188,16 +196,16 @@ mod tests {
|
||||
retrieved_doc
|
||||
.get_first(time_i)
|
||||
.expect("cannot find value")
|
||||
.i64_value(),
|
||||
Some(time_i_val)
|
||||
.i64_value()
|
||||
.expect("value not of i64 type"),
|
||||
time_i_val
|
||||
);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multivalued_i64() -> crate::Result<()> {
|
||||
fn test_multivalued_i64() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field = schema_builder.add_i64_field(
|
||||
"multifield",
|
||||
@@ -205,14 +213,14 @@ mod tests {
|
||||
);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(field=> 1i64, field => 3i64))?;
|
||||
index_writer.add_document(doc!())?;
|
||||
index_writer.add_document(doc!(field=> -4i64))?;
|
||||
index_writer.add_document(doc!(field=> -5i64, field => -20i64, field=>1i64))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(field=> 1i64, field => 3i64));
|
||||
index_writer.add_document(doc!());
|
||||
index_writer.add_document(doc!(field=> -4i64));
|
||||
index_writer.add_document(doc!(field=> -5i64, field => -20i64, field=>1i64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let mut vals = Vec::new();
|
||||
let multi_value_reader = segment_reader.fast_fields().i64s(field).unwrap();
|
||||
@@ -224,10 +232,9 @@ mod tests {
|
||||
assert!(vals.is_empty());
|
||||
multi_value_reader.get_vals(3, &mut vals);
|
||||
assert_eq!(&vals, &[-5i64, -20i64, 1i64]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_multivalued_no_panic(ops: &[IndexingOp]) -> crate::Result<()> {
|
||||
fn test_multivalued_no_panic(ops: &[IndexingOp]) {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field = schema_builder.add_u64_field(
|
||||
"multifield",
|
||||
@@ -237,7 +244,7 @@ mod tests {
|
||||
);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
|
||||
for &op in ops {
|
||||
@@ -245,19 +252,19 @@ mod tests {
|
||||
IndexingOp::AddDoc { id } => {
|
||||
match id % 3 {
|
||||
0 => {
|
||||
index_writer.add_document(doc!())?;
|
||||
index_writer.add_document(doc!());
|
||||
}
|
||||
1 => {
|
||||
let mut doc = Document::new();
|
||||
for _ in 0..5001 {
|
||||
doc.add_u64(field, id as u64);
|
||||
}
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
_ => {
|
||||
let mut doc = Document::new();
|
||||
doc.add_u64(field, id as u64);
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -268,16 +275,18 @@ mod tests {
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
IndexingOp::Merge => {
|
||||
let segment_ids = index.searchable_segment_ids()?;
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
if segment_ids.len() >= 2 {
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
index_writer.segment_updater().wait_merging_thread()?;
|
||||
block_on(index_writer.merge(&segment_ids)).unwrap();
|
||||
assert!(index_writer.segment_updater().wait_merging_thread().is_ok());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
index_writer.commit()?;
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
// Merging the segments
|
||||
{
|
||||
@@ -289,7 +298,6 @@ mod tests {
|
||||
assert!(index_writer.wait_merging_threads().is_ok());
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
@@ -312,7 +320,7 @@ mod tests {
|
||||
proptest! {
|
||||
#[test]
|
||||
fn test_multivalued_proptest(ops in proptest::collection::vec(operation_strategy(), 1..10)) {
|
||||
assert!(test_multivalued_no_panic(&ops[..]).is_ok());
|
||||
test_multivalued_no_panic(&ops[..]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -327,22 +335,20 @@ mod tests {
|
||||
Merge,
|
||||
];
|
||||
|
||||
assert!(test_multivalued_no_panic(&ops[..]).is_ok());
|
||||
test_multivalued_no_panic(&ops[..]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn test_many_facets() -> crate::Result<()> {
|
||||
fn test_many_facets() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field = schema_builder.add_facet_field("facetfield", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
for i in 0..100_000 {
|
||||
index_writer
|
||||
.add_document(doc!(field=> Facet::from(format!("/lang/{}", i).as_str())))?;
|
||||
index_writer.add_document(doc!(field=> Facet::from(format!("/lang/{}", i).as_str())));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
Ok(())
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,22 +94,24 @@ mod tests {
|
||||
use crate::schema::{Cardinality, Facet, IntOptions, Schema, INDEXED};
|
||||
|
||||
#[test]
|
||||
fn test_multifastfield_reader() -> crate::Result<()> {
|
||||
fn test_multifastfield_reader() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let facet_field = schema_builder.add_facet_field("facets", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index
|
||||
.writer_for_tests()
|
||||
.expect("Failed to create index writer.");
|
||||
index_writer.add_document(doc!(
|
||||
facet_field => Facet::from("/category/cat2"),
|
||||
facet_field => Facet::from("/category/cat1"),
|
||||
))?;
|
||||
index_writer.add_document(doc!(facet_field => Facet::from("/category/cat2")))?;
|
||||
index_writer.add_document(doc!(facet_field => Facet::from("/category/cat3")))?;
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
));
|
||||
index_writer.add_document(doc!(facet_field => Facet::from("/category/cat2")));
|
||||
index_writer.add_document(doc!(facet_field => Facet::from("/category/cat3")));
|
||||
index_writer.commit().expect("Commit failed");
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let mut facet_reader = segment_reader.facet_reader(facet_field)?;
|
||||
let mut facet_reader = segment_reader.facet_reader(facet_field).unwrap();
|
||||
|
||||
let mut facet = Facet::root();
|
||||
{
|
||||
@@ -143,11 +145,10 @@ mod tests {
|
||||
facet_reader.facet_ords(2, &mut vals);
|
||||
assert_eq!(&vals[..], &[4]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_multifastfield_reader_min_max() -> crate::Result<()> {
|
||||
fn test_multifastfield_reader_min_max() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field_options = IntOptions::default()
|
||||
.set_indexed()
|
||||
@@ -162,16 +163,15 @@ mod tests {
|
||||
item_field => 2i64,
|
||||
item_field => 3i64,
|
||||
item_field => -2i64,
|
||||
))?;
|
||||
index_writer.add_document(doc!(item_field => 6i64, item_field => 3i64))?;
|
||||
index_writer.add_document(doc!(item_field => 4i64))?;
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
));
|
||||
index_writer.add_document(doc!(item_field => 6i64, item_field => 3i64));
|
||||
index_writer.add_document(doc!(item_field => 4i64));
|
||||
index_writer.commit().expect("Commit failed");
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let field_reader = segment_reader.fast_fields().i64s(item_field)?;
|
||||
let field_reader = segment_reader.fast_fields().i64s(item_field).unwrap();
|
||||
|
||||
assert_eq!(field_reader.min_value(), -2);
|
||||
assert_eq!(field_reader.max_value(), 6);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ fn test_functional_store() -> crate::Result<()> {
|
||||
}
|
||||
for _ in 0..num_docs {
|
||||
doc_set.push(doc_id);
|
||||
index_writer.add_document(doc!(id_field=>doc_id))?;
|
||||
index_writer.add_document(doc!(id_field=>doc_id));
|
||||
doc_id += 1;
|
||||
}
|
||||
index_writer.commit()?;
|
||||
@@ -124,7 +124,7 @@ fn test_functional_indexing_sorted() -> crate::Result<()> {
|
||||
doc.add_u64(multiples_field, random_val * i);
|
||||
}
|
||||
doc.add_text(text_field, get_text());
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
@@ -201,7 +201,7 @@ fn test_functional_indexing_unsorted() -> crate::Result<()> {
|
||||
doc.add_u64(multiples_field, random_val * i);
|
||||
}
|
||||
doc.add_text(text_field, get_text());
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
|
||||
@@ -1,324 +0,0 @@
|
||||
use common::BitSet;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::fastfield::AliveBitSet;
|
||||
use crate::{merge_filtered_segments, Directory, Index, IndexSettings, Segment, SegmentOrdinal};
|
||||
/// DemuxMapping can be used to reorganize data from multiple segments.
|
||||
///
|
||||
/// DemuxMapping is useful in a multitenant settings, in which each document might actually belong to a different tenant.
|
||||
/// It allows to reorganize documents as follows:
|
||||
///
|
||||
/// e.g. if you have two tenant ids TENANT_A and TENANT_B and two segments with
|
||||
/// the documents (simplified)
|
||||
/// Seg 1 [TENANT_A, TENANT_B]
|
||||
/// Seg 2 [TENANT_A, TENANT_B]
|
||||
///
|
||||
/// You may want to group your documents to
|
||||
/// Seg 1 [TENANT_A, TENANT_A]
|
||||
/// Seg 2 [TENANT_B, TENANT_B]
|
||||
///
|
||||
/// Demuxing is the tool for that.
|
||||
/// Semantically you can define a mapping from [old segment ordinal, old doc_id] -> [new segment ordinal].
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DemuxMapping {
|
||||
/// [index old segment ordinal] -> [index doc_id] = new segment ordinal
|
||||
mapping: Vec<DocIdToSegmentOrdinal>,
|
||||
}
|
||||
|
||||
/// DocIdToSegmentOrdinal maps from doc_id within a segment to the new segment ordinal for demuxing.
|
||||
///
|
||||
/// For every source segment there is a `DocIdToSegmentOrdinal` to distribute its doc_ids.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct DocIdToSegmentOrdinal {
|
||||
doc_id_index_to_segment_ord: Vec<SegmentOrdinal>,
|
||||
}
|
||||
|
||||
impl DocIdToSegmentOrdinal {
|
||||
/// Creates a new DocIdToSegmentOrdinal with size of num_doc_ids.
|
||||
/// Initially all doc_ids point to segment ordinal 0 and need to be set
|
||||
/// the via `set` method.
|
||||
pub fn with_max_doc(max_doc: usize) -> Self {
|
||||
DocIdToSegmentOrdinal {
|
||||
doc_id_index_to_segment_ord: vec![0; max_doc],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the number of documents in this mapping.
|
||||
/// It should be equal to the `max_doc` of the segment it targets.
|
||||
pub fn max_doc(&self) -> u32 {
|
||||
self.doc_id_index_to_segment_ord.len() as u32
|
||||
}
|
||||
|
||||
/// Associates a doc_id with an output `SegmentOrdinal`.
|
||||
pub fn set(&mut self, doc_id: u32, segment_ord: SegmentOrdinal) {
|
||||
self.doc_id_index_to_segment_ord[doc_id as usize] = segment_ord;
|
||||
}
|
||||
|
||||
/// Iterates over the new SegmentOrdinal in the order of the doc_id.
|
||||
pub fn iter(&self) -> impl Iterator<Item = SegmentOrdinal> + '_ {
|
||||
self.doc_id_index_to_segment_ord.iter().cloned()
|
||||
}
|
||||
}
|
||||
|
||||
impl DemuxMapping {
|
||||
/// Adds a DocIdToSegmentOrdinal. The order of the pus calls
|
||||
/// defines the old segment ordinal. e.g. first push = ordinal 0.
|
||||
pub fn add(&mut self, segment_mapping: DocIdToSegmentOrdinal) {
|
||||
self.mapping.push(segment_mapping);
|
||||
}
|
||||
|
||||
/// Returns the old number of segments.
|
||||
pub fn get_old_num_segments(&self) -> usize {
|
||||
self.mapping.len()
|
||||
}
|
||||
}
|
||||
|
||||
fn docs_for_segment_ord(
|
||||
doc_id_to_segment_ord: &DocIdToSegmentOrdinal,
|
||||
target_segment_ord: SegmentOrdinal,
|
||||
) -> AliveBitSet {
|
||||
let mut bitset = BitSet::with_max_value(doc_id_to_segment_ord.max_doc());
|
||||
for doc_id in doc_id_to_segment_ord
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(|(_doc_id, new_segment_ord)| *new_segment_ord == target_segment_ord)
|
||||
.map(|(doc_id, _)| doc_id)
|
||||
{
|
||||
// add document if segment ordinal = target segment ordinal
|
||||
bitset.insert(doc_id as u32);
|
||||
}
|
||||
AliveBitSet::from_bitset(&bitset)
|
||||
}
|
||||
|
||||
fn get_alive_bitsets(
|
||||
demux_mapping: &DemuxMapping,
|
||||
target_segment_ord: SegmentOrdinal,
|
||||
) -> Vec<AliveBitSet> {
|
||||
demux_mapping
|
||||
.mapping
|
||||
.iter()
|
||||
.map(|doc_id_to_segment_ord| {
|
||||
docs_for_segment_ord(doc_id_to_segment_ord, target_segment_ord)
|
||||
})
|
||||
.collect_vec()
|
||||
}
|
||||
|
||||
/// Demux the segments according to `demux_mapping`. See `DemuxMapping`.
|
||||
/// The number of output_directories need to match max new segment ordinal from `demux_mapping`.
|
||||
///
|
||||
/// The ordinal of `segments` need to match the ordinals provided in `demux_mapping`.
|
||||
pub fn demux(
|
||||
segments: &[Segment],
|
||||
demux_mapping: &DemuxMapping,
|
||||
target_settings: IndexSettings,
|
||||
output_directories: Vec<Box<dyn Directory>>,
|
||||
) -> crate::Result<Vec<Index>> {
|
||||
let mut indices = vec![];
|
||||
for (target_segment_ord, output_directory) in output_directories.into_iter().enumerate() {
|
||||
let delete_bitsets = get_alive_bitsets(demux_mapping, target_segment_ord as u32)
|
||||
.into_iter()
|
||||
.map(Some)
|
||||
.collect_vec();
|
||||
let index = merge_filtered_segments(
|
||||
segments,
|
||||
target_settings.clone(),
|
||||
delete_bitsets,
|
||||
output_directory,
|
||||
)?;
|
||||
indices.push(index);
|
||||
}
|
||||
Ok(indices)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
collector::TopDocs,
|
||||
directory::RamDirectory,
|
||||
query::QueryParser,
|
||||
schema::{Schema, TEXT},
|
||||
DocAddress, Term,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_demux_map_to_deletebitset() {
|
||||
let max_value = 2;
|
||||
let mut demux_mapping = DemuxMapping::default();
|
||||
//segment ordinal 0 mapping
|
||||
let mut doc_id_to_segment = DocIdToSegmentOrdinal::with_max_doc(max_value);
|
||||
doc_id_to_segment.set(0, 1);
|
||||
doc_id_to_segment.set(1, 0);
|
||||
demux_mapping.add(doc_id_to_segment);
|
||||
|
||||
//segment ordinal 1 mapping
|
||||
let mut doc_id_to_segment = DocIdToSegmentOrdinal::with_max_doc(max_value);
|
||||
doc_id_to_segment.set(0, 1);
|
||||
doc_id_to_segment.set(1, 1);
|
||||
demux_mapping.add(doc_id_to_segment);
|
||||
{
|
||||
let bit_sets_for_demuxing_to_segment_ord_0 = get_alive_bitsets(&demux_mapping, 0);
|
||||
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_0[0].is_deleted(0),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_0[0].is_deleted(1),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_0[1].is_deleted(0),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_0[1].is_deleted(1),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
||||
{
|
||||
let bit_sets_for_demuxing_to_segment_ord_1 = get_alive_bitsets(&demux_mapping, 1);
|
||||
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_1[0].is_deleted(0),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_1[0].is_deleted(1),
|
||||
true
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_1[1].is_deleted(0),
|
||||
false
|
||||
);
|
||||
assert_eq!(
|
||||
bit_sets_for_demuxing_to_segment_ord_1[1].is_deleted(1),
|
||||
false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_demux_segments() -> crate::Result<()> {
|
||||
let first_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"texto1"))?;
|
||||
index_writer.add_document(doc!(text_field=>"texto2"))?;
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let second_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"texto3"))?;
|
||||
index_writer.add_document(doc!(text_field=>"texto4"))?;
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "4"));
|
||||
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let mut segments: Vec<Segment> = Vec::new();
|
||||
segments.extend(first_index.searchable_segments()?);
|
||||
segments.extend(second_index.searchable_segments()?);
|
||||
|
||||
let target_settings = first_index.settings().clone();
|
||||
|
||||
let mut demux_mapping = DemuxMapping::default();
|
||||
{
|
||||
let max_value = 2;
|
||||
//segment ordinal 0 mapping
|
||||
let mut doc_id_to_segment = DocIdToSegmentOrdinal::with_max_doc(max_value);
|
||||
doc_id_to_segment.set(0, 1);
|
||||
doc_id_to_segment.set(1, 0);
|
||||
demux_mapping.add(doc_id_to_segment);
|
||||
|
||||
//segment ordinal 1 mapping
|
||||
let mut doc_id_to_segment = DocIdToSegmentOrdinal::with_max_doc(max_value);
|
||||
doc_id_to_segment.set(0, 1);
|
||||
doc_id_to_segment.set(1, 1);
|
||||
demux_mapping.add(doc_id_to_segment);
|
||||
}
|
||||
assert_eq!(demux_mapping.get_old_num_segments(), 2);
|
||||
|
||||
let demuxed_indices = demux(
|
||||
&segments,
|
||||
&demux_mapping,
|
||||
target_settings,
|
||||
vec![
|
||||
Box::new(RamDirectory::default()),
|
||||
Box::new(RamDirectory::default()),
|
||||
],
|
||||
)?;
|
||||
|
||||
{
|
||||
let index = &demuxed_indices[0];
|
||||
|
||||
let segments = index.searchable_segments()?;
|
||||
assert_eq!(segments.len(), 1);
|
||||
|
||||
let segment_metas = segments[0].meta();
|
||||
assert_eq!(segment_metas.num_deleted_docs(), 0);
|
||||
assert_eq!(segment_metas.num_docs(), 1);
|
||||
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
{
|
||||
let text_field = index.schema().get_field("text").unwrap();
|
||||
|
||||
let do_search = |term: &str| {
|
||||
let query = QueryParser::for_index(&index, vec![text_field])
|
||||
.parse_query(term)
|
||||
.unwrap();
|
||||
let top_docs: Vec<(f32, DocAddress)> =
|
||||
searcher.search(&query, &TopDocs::with_limit(3)).unwrap();
|
||||
|
||||
top_docs.iter().map(|el| el.1.doc_id).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
assert_eq!(do_search("texto1"), vec![] as Vec<u32>);
|
||||
assert_eq!(do_search("texto2"), vec![0]);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
let index = &demuxed_indices[1];
|
||||
|
||||
let segments = index.searchable_segments()?;
|
||||
assert_eq!(segments.len(), 1);
|
||||
|
||||
let segment_metas = segments[0].meta();
|
||||
assert_eq!(segment_metas.num_deleted_docs(), 0);
|
||||
assert_eq!(segment_metas.num_docs(), 3);
|
||||
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
{
|
||||
let text_field = index.schema().get_field("text").unwrap();
|
||||
|
||||
let do_search = |term: &str| {
|
||||
let query = QueryParser::for_index(&index, vec![text_field])
|
||||
.parse_query(term)
|
||||
.unwrap();
|
||||
let top_docs: Vec<(f32, DocAddress)> =
|
||||
searcher.search(&query, &TopDocs::with_limit(3)).unwrap();
|
||||
|
||||
top_docs.iter().map(|el| el.1.doc_id).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
assert_eq!(do_search("texto1"), vec![0]);
|
||||
assert_eq!(do_search("texto2"), vec![] as Vec<u32>);
|
||||
assert_eq!(do_search("texto3"), vec![1]);
|
||||
assert_eq!(do_search("texto4"), vec![2]);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -11,12 +11,12 @@ use std::{cmp::Reverse, ops::Index};
|
||||
|
||||
/// Struct to provide mapping from new doc_id to old doc_id and segment.
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct SegmentDocIdMapping {
|
||||
pub(crate) struct SegmentDocidMapping {
|
||||
new_doc_id_to_old_and_segment: Vec<(DocId, SegmentOrdinal)>,
|
||||
is_trivial: bool,
|
||||
}
|
||||
|
||||
impl SegmentDocIdMapping {
|
||||
impl SegmentDocidMapping {
|
||||
pub(crate) fn new(
|
||||
new_doc_id_to_old_and_segment: Vec<(DocId, SegmentOrdinal)>,
|
||||
is_trivial: bool,
|
||||
@@ -40,14 +40,14 @@ impl SegmentDocIdMapping {
|
||||
self.is_trivial
|
||||
}
|
||||
}
|
||||
impl Index<usize> for SegmentDocIdMapping {
|
||||
impl Index<usize> for SegmentDocidMapping {
|
||||
type Output = (DocId, SegmentOrdinal);
|
||||
|
||||
fn index(&self, idx: usize) -> &Self::Output {
|
||||
&self.new_doc_id_to_old_and_segment[idx]
|
||||
}
|
||||
}
|
||||
impl IntoIterator for SegmentDocIdMapping {
|
||||
impl IntoIterator for SegmentDocidMapping {
|
||||
type Item = (DocId, SegmentOrdinal);
|
||||
type IntoIter = std::vec::IntoIter<Self::Item>;
|
||||
|
||||
@@ -146,7 +146,7 @@ mod tests_indexsorting {
|
||||
fn create_test_index(
|
||||
index_settings: Option<IndexSettings>,
|
||||
text_field_options: TextOptions,
|
||||
) -> crate::Result<Index> {
|
||||
) -> Index {
|
||||
let mut schema_builder = Schema::builder();
|
||||
|
||||
let my_text_field = schema_builder.add_text_field("text_field", text_field_options);
|
||||
@@ -166,20 +166,19 @@ mod tests_indexsorting {
|
||||
if let Some(settings) = index_settings {
|
||||
index_builder = index_builder.settings(settings);
|
||||
}
|
||||
let index = index_builder.create_in_ram()?;
|
||||
let index = index_builder.create_in_ram().unwrap();
|
||||
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(my_number=>40_u64))?;
|
||||
index_writer.add_document(
|
||||
doc!(my_number=>20_u64, multi_numbers => 5_u64, multi_numbers => 6_u64),
|
||||
)?;
|
||||
index_writer.add_document(doc!(my_number=>100_u64))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(my_number=>40_u64));
|
||||
index_writer
|
||||
.add_document(doc!(my_number=>20_u64, multi_numbers => 5_u64, multi_numbers => 6_u64));
|
||||
index_writer.add_document(doc!(my_number=>100_u64));
|
||||
index_writer.add_document(
|
||||
doc!(my_number=>10_u64, my_string_field=> "blublub", my_text_field => "some text"),
|
||||
)?;
|
||||
index_writer.add_document(doc!(my_number=>30_u64, multi_numbers => 3_u64 ))?;
|
||||
index_writer.commit()?;
|
||||
Ok(index)
|
||||
);
|
||||
index_writer.add_document(doc!(my_number=>30_u64, multi_numbers => 3_u64 ));
|
||||
index_writer.commit().unwrap();
|
||||
index
|
||||
}
|
||||
fn get_text_options() -> TextOptions {
|
||||
TextOptions::default().set_indexing_options(
|
||||
@@ -204,7 +203,7 @@ mod tests_indexsorting {
|
||||
for option in options {
|
||||
//let options = get_text_options();
|
||||
// no index_sort
|
||||
let index = create_test_index(None, option.clone())?;
|
||||
let index = create_test_index(None, option.clone());
|
||||
let my_text_field = index.schema().get_field("text_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
|
||||
@@ -226,7 +225,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
option.clone(),
|
||||
)?;
|
||||
);
|
||||
let my_text_field = index.schema().get_field("text_field").unwrap();
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -258,7 +257,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
option.clone(),
|
||||
)?;
|
||||
);
|
||||
let my_string_field = index.schema().get_field("text_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
|
||||
@@ -288,7 +287,7 @@ mod tests_indexsorting {
|
||||
#[test]
|
||||
fn test_sort_index_get_documents() -> crate::Result<()> {
|
||||
// default baseline
|
||||
let index = create_test_index(None, get_text_options())?;
|
||||
let index = create_test_index(None, get_text_options());
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
{
|
||||
@@ -317,7 +316,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
get_text_options(),
|
||||
)?;
|
||||
);
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
{
|
||||
@@ -342,7 +341,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
get_text_options(),
|
||||
)?;
|
||||
);
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
{
|
||||
@@ -357,7 +356,7 @@ mod tests_indexsorting {
|
||||
|
||||
#[test]
|
||||
fn test_sort_index_test_string_field() -> crate::Result<()> {
|
||||
let index = create_test_index(None, get_text_options())?;
|
||||
let index = create_test_index(None, get_text_options());
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
|
||||
@@ -377,7 +376,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
get_text_options(),
|
||||
)?;
|
||||
);
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -408,7 +407,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
get_text_options(),
|
||||
)?;
|
||||
);
|
||||
let my_string_field = index.schema().get_field("string_field").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
|
||||
@@ -444,7 +443,7 @@ mod tests_indexsorting {
|
||||
..Default::default()
|
||||
}),
|
||||
get_text_options(),
|
||||
)?;
|
||||
);
|
||||
assert_eq!(
|
||||
index.settings().sort_by_field.as_ref().unwrap().field,
|
||||
"my_number".to_string()
|
||||
|
||||
@@ -14,7 +14,6 @@ use crate::error::TantivyError;
|
||||
use crate::fastfield::write_alive_bitset;
|
||||
use crate::indexer::delete_queue::{DeleteCursor, DeleteQueue};
|
||||
use crate::indexer::doc_opstamp_mapping::DocToOpstampMapping;
|
||||
use crate::indexer::index_writer_status::IndexWriterStatus;
|
||||
use crate::indexer::operation::DeleteOperation;
|
||||
use crate::indexer::stamper::Stamper;
|
||||
use crate::indexer::MergePolicy;
|
||||
@@ -29,13 +28,16 @@ use crossbeam::channel;
|
||||
use futures::executor::block_on;
|
||||
use futures::future::Future;
|
||||
use smallvec::smallvec;
|
||||
use smallvec::SmallVec;
|
||||
use wasm_mt_pool::pool_exec;
|
||||
use wasm_mt::prelude::*;
|
||||
use std::mem;
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
use wasm_mt_pool::prelude::*;
|
||||
use std::thread;
|
||||
use std::thread::JoinHandle;
|
||||
|
||||
use super::{AddBatch, AddBatchReceiver, AddBatchSender};
|
||||
|
||||
// Size of the margin for the heap. A segment is closed when the remaining memory
|
||||
// in the heap goes below MARGIN_IN_BYTES.
|
||||
pub const MARGIN_IN_BYTES: usize = 1_000_000;
|
||||
@@ -51,12 +53,15 @@ pub const MAX_NUM_THREAD: usize = 8;
|
||||
// reaches `PIPELINE_MAX_SIZE_IN_DOCS`
|
||||
const PIPELINE_MAX_SIZE_IN_DOCS: usize = 10_000;
|
||||
|
||||
fn error_in_index_worker_thread(context: &str) -> TantivyError {
|
||||
TantivyError::ErrorInThread(format!(
|
||||
"{}. A worker thread encounterred an error (io::Error most likely) or panicked.",
|
||||
context
|
||||
))
|
||||
}
|
||||
// Group of operations.
|
||||
// Most of the time, users will send operation one-by-one, but it can be useful to
|
||||
// send them as a small block to ensure that
|
||||
// - all docs in the operation will happen on the same segment and continuous doc_ids.
|
||||
// - all operations in the group are committed at the same time, making the group
|
||||
// atomic.
|
||||
type OperationGroup = SmallVec<[AddOperation; 4]>;
|
||||
type OperationSender = channel::Sender<OperationGroup>;
|
||||
type OperationReceiver = channel::Receiver<OperationGroup>;
|
||||
|
||||
/// `IndexWriter` is the user entry-point to add document to an index.
|
||||
///
|
||||
@@ -73,10 +78,10 @@ pub struct IndexWriter {
|
||||
|
||||
heap_size_in_bytes_per_thread: usize,
|
||||
|
||||
workers_join_handle: Vec<JoinHandle<crate::Result<()>>>,
|
||||
workers_join_handle: Vec<JoinHandle<Result<JsValue, JsValue>>>,
|
||||
|
||||
index_writer_status: IndexWriterStatus,
|
||||
operation_sender: AddBatchSender,
|
||||
operation_receiver: OperationReceiver,
|
||||
operation_sender: OperationSender,
|
||||
|
||||
segment_updater: SegmentUpdater,
|
||||
|
||||
@@ -88,6 +93,8 @@ pub struct IndexWriter {
|
||||
|
||||
stamper: Stamper,
|
||||
committed_opstamp: Opstamp,
|
||||
|
||||
worker_pool: wasm_mt_pool::ThreadPool,
|
||||
}
|
||||
|
||||
fn compute_deleted_bitset(
|
||||
@@ -162,8 +169,15 @@ pub(crate) fn advance_deletes(
|
||||
target_opstamp,
|
||||
)?;
|
||||
|
||||
// TODO optimize
|
||||
// It should be possible to do something smarter by manipulation bitsets directly
|
||||
// to compute this union.
|
||||
if let Some(seg_alive_bitset) = segment_reader.alive_bitset() {
|
||||
alive_bitset.intersect_update(seg_alive_bitset.bitset());
|
||||
for doc in 0u32..max_doc {
|
||||
if seg_alive_bitset.is_deleted(doc) {
|
||||
alive_bitset.remove(doc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let num_alive_docs: u32 = alive_bitset.len() as u32;
|
||||
@@ -183,10 +197,10 @@ pub(crate) fn advance_deletes(
|
||||
fn index_documents(
|
||||
memory_budget: usize,
|
||||
segment: Segment,
|
||||
grouped_document_iterator: &mut dyn Iterator<Item = AddBatch>,
|
||||
grouped_document_iterator: &mut dyn Iterator<Item = OperationGroup>,
|
||||
segment_updater: &mut SegmentUpdater,
|
||||
mut delete_cursor: DeleteCursor,
|
||||
) -> crate::Result<()> {
|
||||
) -> crate::Result<bool> {
|
||||
let schema = segment.schema();
|
||||
|
||||
let mut segment_writer = SegmentWriter::for_segment(memory_budget, segment.clone(), &schema)?;
|
||||
@@ -205,7 +219,7 @@ fn index_documents(
|
||||
}
|
||||
|
||||
if !segment_updater.is_alive() {
|
||||
return Ok(());
|
||||
return Ok(false);
|
||||
}
|
||||
|
||||
let max_doc = segment_writer.max_doc();
|
||||
@@ -225,13 +239,13 @@ fn index_documents(
|
||||
// update segment_updater inventory to remove tempstore
|
||||
let segment_entry = SegmentEntry::new(meta, delete_cursor, alive_bitset_opt);
|
||||
block_on(segment_updater.schedule_add_segment(segment_entry))?;
|
||||
Ok(())
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// `doc_opstamps` is required to be non-empty.
|
||||
fn apply_deletes(
|
||||
segment: &Segment,
|
||||
delete_cursor: &mut DeleteCursor,
|
||||
mut delete_cursor: &mut DeleteCursor,
|
||||
doc_opstamps: &[Opstamp],
|
||||
) -> crate::Result<Option<BitSet>> {
|
||||
if delete_cursor.get().is_none() {
|
||||
@@ -254,7 +268,7 @@ fn apply_deletes(
|
||||
let may_have_deletes = compute_deleted_bitset(
|
||||
&mut deleted_bitset,
|
||||
&segment_reader,
|
||||
delete_cursor,
|
||||
&mut delete_cursor,
|
||||
&doc_to_opstamps,
|
||||
max_doc_opstamp,
|
||||
)?;
|
||||
@@ -278,7 +292,8 @@ impl IndexWriter {
|
||||
/// should work at the same time.
|
||||
/// # Errors
|
||||
/// If the lockfile already exists, returns `Error::FileAlreadyExists`.
|
||||
/// If the heap size per thread is too small or too big, returns `TantivyError::InvalidArgument`
|
||||
/// # Panics
|
||||
/// If the heap size per thread is too small, panics.
|
||||
pub(crate) fn new(
|
||||
index: &Index,
|
||||
num_threads: usize,
|
||||
@@ -296,7 +311,7 @@ impl IndexWriter {
|
||||
let err_msg = format!("The heap size per thread cannot exceed {}", HEAP_SIZE_MAX);
|
||||
return Err(TantivyError::InvalidArgument(err_msg));
|
||||
}
|
||||
let (document_sender, document_receiver): (AddBatchSender, AddBatchReceiver) =
|
||||
let (document_sender, document_receiver): (OperationSender, OperationReceiver) =
|
||||
channel::bounded(PIPELINE_MAX_SIZE_IN_DOCS);
|
||||
|
||||
let delete_queue = DeleteQueue::new();
|
||||
@@ -308,13 +323,14 @@ impl IndexWriter {
|
||||
let segment_updater =
|
||||
SegmentUpdater::create(index.clone(), stamper.clone(), &delete_queue.cursor())?;
|
||||
|
||||
let worker_pool = block_on(wasm_mt_pool::ThreadPool::new(num_threads, crate::PKG_JS).and_init()).unwrap();
|
||||
let mut index_writer = IndexWriter {
|
||||
_directory_lock: Some(directory_lock),
|
||||
|
||||
heap_size_in_bytes_per_thread,
|
||||
index: index.clone(),
|
||||
|
||||
index_writer_status: IndexWriterStatus::from(document_receiver),
|
||||
operation_receiver: document_receiver,
|
||||
operation_sender: document_sender,
|
||||
|
||||
segment_updater,
|
||||
@@ -328,6 +344,7 @@ impl IndexWriter {
|
||||
stamper,
|
||||
|
||||
worker_id: 0,
|
||||
worker_pool,
|
||||
};
|
||||
index_writer.start_workers()?;
|
||||
Ok(index_writer)
|
||||
@@ -354,14 +371,16 @@ impl IndexWriter {
|
||||
for join_handle in former_workers_handles {
|
||||
join_handle
|
||||
.join()
|
||||
.map_err(|_| error_in_index_worker_thread("Worker thread panicked."))?
|
||||
.map_err(|_| error_in_index_worker_thread("Worker thread failed."))?;
|
||||
.expect("Indexing Worker thread panicked")
|
||||
.map_err(|_| {
|
||||
TantivyError::ErrorInThread("Error in indexing worker thread.".into())
|
||||
})?;
|
||||
}
|
||||
|
||||
let result = self
|
||||
.segment_updater
|
||||
.wait_merging_thread()
|
||||
.map_err(|_| error_in_index_worker_thread("Failed to join merging thread."));
|
||||
.map_err(|_| TantivyError::ErrorInThread("Failed to join merging thread.".into()));
|
||||
|
||||
if let Err(ref e) = result {
|
||||
error!("Some merging thread failed {:?}", e);
|
||||
@@ -389,53 +408,45 @@ impl IndexWriter {
|
||||
self.index.new_segment()
|
||||
}
|
||||
|
||||
fn operation_receiver(&self) -> crate::Result<AddBatchReceiver> {
|
||||
self.index_writer_status
|
||||
.operation_receiver()
|
||||
.ok_or_else(|| crate::TantivyError::ErrorInThread("The index writer was killed. It can happen if an indexing worker encounterred an Io error for instance.".to_string()))
|
||||
}
|
||||
|
||||
/// Spawns a new worker thread for indexing.
|
||||
/// The thread consumes documents from the pipeline.
|
||||
fn add_indexing_worker(&mut self) -> crate::Result<()> {
|
||||
let document_receiver_clone = self.operation_receiver()?;
|
||||
let index_writer_bomb = self.index_writer_status.create_bomb();
|
||||
|
||||
let document_receiver_clone = self.operation_receiver.clone();
|
||||
let mut segment_updater = self.segment_updater.clone();
|
||||
|
||||
let mut delete_cursor = self.delete_queue.cursor();
|
||||
|
||||
let mem_budget = self.heap_size_in_bytes_per_thread;
|
||||
let index = self.index.clone();
|
||||
let join_handle: JoinHandle<crate::Result<()>> = thread::Builder::new()
|
||||
.name(format!("thrd-tantivy-index{}", self.worker_id))
|
||||
.spawn(move || {
|
||||
let join_handle: JoinHandle<crate::Result<_>> = pool_exec!(self.worker_pool,
|
||||
move || {
|
||||
loop {
|
||||
let mut document_iterator = document_receiver_clone
|
||||
.clone()
|
||||
.into_iter()
|
||||
.filter(|batch| !batch.is_empty())
|
||||
.peekable();
|
||||
let mut document_iterator =
|
||||
document_receiver_clone.clone().into_iter().peekable();
|
||||
|
||||
// The peeking here is to avoid creating a new segment's files
|
||||
// the peeking here is to avoid
|
||||
// creating a new segment's files
|
||||
// if no document are available.
|
||||
//
|
||||
// This is a valid guarantee as the peeked document now belongs to
|
||||
// this is a valid guarantee as the
|
||||
// peeked document now belongs to
|
||||
// our local iterator.
|
||||
if let Some(batch) = document_iterator.peek() {
|
||||
assert!(!batch.is_empty());
|
||||
delete_cursor.skip_to(batch[0].opstamp);
|
||||
if let Some(operations) = document_iterator.peek() {
|
||||
if let Some(first) = operations.first() {
|
||||
delete_cursor.skip_to(first.opstamp);
|
||||
} else {
|
||||
return Ok(());
|
||||
}
|
||||
} else {
|
||||
// No more documents.
|
||||
// It happens when there is a commit, or if the `IndexWriter`
|
||||
// Happens when there is a commit, or if the `IndexWriter`
|
||||
// was dropped.
|
||||
index_writer_bomb.defuse();
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let segment = index.new_segment();
|
||||
index_documents(
|
||||
mem_budget,
|
||||
index.new_segment(),
|
||||
segment,
|
||||
&mut document_iterator,
|
||||
&mut segment_updater,
|
||||
delete_cursor.clone(),
|
||||
@@ -465,8 +476,10 @@ impl IndexWriter {
|
||||
}
|
||||
|
||||
/// Detects and removes the files that are not used by the index anymore.
|
||||
pub async fn garbage_collect_files(&self) -> crate::Result<GarbageCollectionResult> {
|
||||
self.segment_updater.schedule_garbage_collect().await
|
||||
pub fn garbage_collect_files(
|
||||
&self,
|
||||
) -> impl Future<Output = crate::Result<GarbageCollectionResult>> {
|
||||
self.segment_updater.schedule_garbage_collect()
|
||||
}
|
||||
|
||||
/// Deletes all documents from the index
|
||||
@@ -489,7 +502,7 @@ impl IndexWriter {
|
||||
/// let index = Index::create_in_ram(schema.clone());
|
||||
///
|
||||
/// let mut index_writer = index.writer_with_num_threads(1, 50_000_000)?;
|
||||
/// index_writer.add_document(doc!(title => "The modern Promotheus"))?;
|
||||
/// index_writer.add_document(doc!(title => "The modern Promotheus"));
|
||||
/// index_writer.commit()?;
|
||||
///
|
||||
/// let clear_res = index_writer.delete_all_documents().unwrap();
|
||||
@@ -533,11 +546,12 @@ impl IndexWriter {
|
||||
/// when no documents are remaining.
|
||||
///
|
||||
/// Returns the former segment_ready channel.
|
||||
fn recreate_document_channel(&mut self) {
|
||||
let (document_sender, document_receiver): (AddBatchSender, AddBatchReceiver) =
|
||||
#[allow(unused_must_use)]
|
||||
fn recreate_document_channel(&mut self) -> OperationReceiver {
|
||||
let (document_sender, document_receiver): (OperationSender, OperationReceiver) =
|
||||
channel::bounded(PIPELINE_MAX_SIZE_IN_DOCS);
|
||||
self.operation_sender = document_sender;
|
||||
self.index_writer_status = IndexWriterStatus::from(document_receiver);
|
||||
mem::replace(&mut self.operation_sender, document_sender);
|
||||
mem::replace(&mut self.operation_receiver, document_receiver)
|
||||
}
|
||||
|
||||
/// Rollback to the last commit
|
||||
@@ -553,7 +567,7 @@ impl IndexWriter {
|
||||
// marks the segment updater as killed. From now on, all
|
||||
// segment updates will be ignored.
|
||||
self.segment_updater.kill();
|
||||
let document_receiver = self.operation_receiver();
|
||||
let document_receiver = self.operation_receiver.clone();
|
||||
|
||||
// take the directory lock to create a new index_writer.
|
||||
let directory_lock = self
|
||||
@@ -693,10 +707,14 @@ impl IndexWriter {
|
||||
/// The opstamp is an increasing `u64` that can
|
||||
/// be used by the client to align commits with its own
|
||||
/// document queue.
|
||||
pub fn add_document(&self, document: Document) -> crate::Result<Opstamp> {
|
||||
pub fn add_document(&self, document: Document) -> Opstamp {
|
||||
let opstamp = self.stamper.stamp();
|
||||
self.send_add_documents_batch(smallvec![AddOperation { opstamp, document }])?;
|
||||
Ok(opstamp)
|
||||
let add_operation = AddOperation { opstamp, document };
|
||||
let send_result = self.operation_sender.send(smallvec![add_operation]);
|
||||
if let Err(e) = send_result {
|
||||
panic!("Failed to index document. Sending to indexing channel failed. This probably means all of the indexing threads have panicked. {:?}", e);
|
||||
}
|
||||
opstamp
|
||||
}
|
||||
|
||||
/// Gets a range of stamps from the stamper and "pops" the last stamp
|
||||
@@ -709,7 +727,11 @@ impl IndexWriter {
|
||||
fn get_batch_opstamps(&self, count: Opstamp) -> (Opstamp, Range<Opstamp>) {
|
||||
let Range { start, end } = self.stamper.stamps(count + 1u64);
|
||||
let last_opstamp = end - 1;
|
||||
(last_opstamp, start..last_opstamp)
|
||||
let stamps = Range {
|
||||
start,
|
||||
end: last_opstamp,
|
||||
};
|
||||
(last_opstamp, stamps)
|
||||
}
|
||||
|
||||
/// Runs a group of document operations ensuring that the operations are
|
||||
@@ -728,20 +750,16 @@ impl IndexWriter {
|
||||
/// Like adds and deletes (see `IndexWriter.add_document` and
|
||||
/// `IndexWriter.delete_term`), the changes made by calling `run` will be
|
||||
/// visible to readers only after calling `commit()`.
|
||||
pub fn run<I>(&self, user_operations: I) -> crate::Result<Opstamp>
|
||||
where
|
||||
I: IntoIterator<Item = UserOperation>,
|
||||
I::IntoIter: ExactSizeIterator,
|
||||
{
|
||||
let user_operations_it = user_operations.into_iter();
|
||||
let count = user_operations_it.len() as u64;
|
||||
pub fn run(&self, user_operations: Vec<UserOperation>) -> Opstamp {
|
||||
let count = user_operations.len() as u64;
|
||||
if count == 0 {
|
||||
return Ok(self.stamper.stamp());
|
||||
return self.stamper.stamp();
|
||||
}
|
||||
let (batch_opstamp, stamps) = self.get_batch_opstamps(count);
|
||||
|
||||
let mut adds = AddBatch::default();
|
||||
for (user_op, opstamp) in user_operations_it.zip(stamps) {
|
||||
let mut adds = OperationGroup::default();
|
||||
|
||||
for (user_op, opstamp) in user_operations.into_iter().zip(stamps) {
|
||||
match user_op {
|
||||
UserOperation::Delete(term) => {
|
||||
let delete_operation = DeleteOperation { opstamp, term };
|
||||
@@ -753,16 +771,12 @@ impl IndexWriter {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.send_add_documents_batch(adds)?;
|
||||
Ok(batch_opstamp)
|
||||
}
|
||||
let send_result = self.operation_sender.send(adds);
|
||||
if let Err(e) = send_result {
|
||||
panic!("Failed to index document. Sending to indexing channel failed. This probably means all of the indexing threads have panicked. {:?}", e);
|
||||
};
|
||||
|
||||
fn send_add_documents_batch(&self, add_ops: AddBatch) -> crate::Result<()> {
|
||||
if self.index_writer_status.is_alive() && self.operation_sender.send(add_ops).is_ok() {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(error_in_index_worker_thread("An index writer was killed."))
|
||||
}
|
||||
batch_opstamp
|
||||
}
|
||||
}
|
||||
|
||||
@@ -828,7 +842,7 @@ mod tests {
|
||||
UserOperation::Add(doc!(text_field=>"a")),
|
||||
UserOperation::Add(doc!(text_field=>"b")),
|
||||
];
|
||||
let batch_opstamp1 = index_writer.run(operations).unwrap();
|
||||
let batch_opstamp1 = index_writer.run(operations);
|
||||
assert_eq!(batch_opstamp1, 2u64);
|
||||
}
|
||||
|
||||
@@ -839,12 +853,8 @@ mod tests {
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(text_field => "hello1"))
|
||||
.unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(text_field => "hello2"))
|
||||
.unwrap();
|
||||
index_writer.add_document(doc!(text_field => "hello1"));
|
||||
index_writer.add_document(doc!(text_field => "hello2"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let reader = index.reader().unwrap();
|
||||
@@ -901,7 +911,7 @@ mod tests {
|
||||
UserOperation::Delete(b_term),
|
||||
];
|
||||
|
||||
index_writer.run(operations).unwrap();
|
||||
index_writer.run(operations);
|
||||
index_writer.commit().expect("failed to commit");
|
||||
reader.reload().expect("failed to load searchers");
|
||||
|
||||
@@ -931,10 +941,10 @@ mod tests {
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let index_writer = index.writer(3_000_000).unwrap();
|
||||
let operations1 = vec![];
|
||||
let batch_opstamp1 = index_writer.run(operations1).unwrap();
|
||||
let batch_opstamp1 = index_writer.run(operations1);
|
||||
assert_eq!(batch_opstamp1, 0u64);
|
||||
let operations2 = vec![];
|
||||
let batch_opstamp2 = index_writer.run(operations2).unwrap();
|
||||
let batch_opstamp2 = index_writer.run(operations2);
|
||||
assert_eq!(batch_opstamp2, 1u64);
|
||||
}
|
||||
|
||||
@@ -994,14 +1004,15 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_commit_and_rollback() -> crate::Result<()> {
|
||||
fn test_commit_and_rollback() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let reader = index
|
||||
.reader_builder()
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?;
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let num_docs_containing = |s: &str| {
|
||||
let searcher = reader.searcher();
|
||||
let term = Term::from_field_text(text_field, s);
|
||||
@@ -1010,127 +1021,136 @@ mod tests {
|
||||
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer(3_000_000)?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.rollback()?;
|
||||
let mut index_writer = index.writer(3_000_000).unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.rollback().unwrap();
|
||||
assert_eq!(index_writer.commit_opstamp(), 0u64);
|
||||
assert_eq!(num_docs_containing("a"), 0);
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"c"))?;
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
{
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
index_writer.add_document(doc!(text_field=>"c"));
|
||||
}
|
||||
assert!(index_writer.commit().is_ok());
|
||||
reader.reload().unwrap();
|
||||
assert_eq!(num_docs_containing("a"), 0);
|
||||
assert_eq!(num_docs_containing("b"), 1);
|
||||
assert_eq!(num_docs_containing("c"), 1);
|
||||
}
|
||||
reader.reload()?;
|
||||
reader.reload().unwrap();
|
||||
reader.searcher();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_with_merges() -> crate::Result<()> {
|
||||
fn test_with_merges() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let reader = index
|
||||
.reader_builder()
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?;
|
||||
.try_into()
|
||||
.unwrap();
|
||||
let num_docs_containing = |s: &str| {
|
||||
let term_a = Term::from_field_text(text_field, s);
|
||||
reader.searcher().doc_freq(&term_a).unwrap()
|
||||
};
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer(12_000_000).unwrap();
|
||||
// create 8 segments with 100 tiny docs
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer(12_000_000).unwrap();
|
||||
// create 8 segments with 100 tiny docs
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
}
|
||||
index_writer.commit().expect("commit failed");
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
}
|
||||
// this should create 8 segments and trigger a merge.
|
||||
index_writer.commit().expect("commit failed");
|
||||
index_writer
|
||||
.wait_merging_threads()
|
||||
.expect("waiting merging thread failed");
|
||||
|
||||
reader.reload().unwrap();
|
||||
|
||||
assert_eq!(num_docs_containing("a"), 200);
|
||||
assert!(index.searchable_segments().unwrap().len() < 8);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
}
|
||||
// this should create 8 segments and trigger a merge.
|
||||
index_writer.commit()?;
|
||||
index_writer.wait_merging_threads()?;
|
||||
reader.reload()?;
|
||||
assert_eq!(num_docs_containing("a"), 200);
|
||||
assert!(index.searchable_segments()?.len() < 8);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_with_commit_message() -> crate::Result<()> {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer(12_000_000)?;
|
||||
// create 8 segments with 100 tiny docs
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "a"))?;
|
||||
}
|
||||
{
|
||||
let mut prepared_commit = index_writer.prepare_commit()?;
|
||||
prepared_commit.set_payload("first commit");
|
||||
prepared_commit.commit()?;
|
||||
}
|
||||
{
|
||||
let metas = index.load_metas()?;
|
||||
assert_eq!(metas.payload.unwrap(), "first commit");
|
||||
}
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "a"))?;
|
||||
}
|
||||
index_writer.commit()?;
|
||||
{
|
||||
let metas = index.load_metas()?;
|
||||
assert!(metas.payload.is_none());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_but_rollback() -> crate::Result<()> {
|
||||
fn test_prepare_with_commit_message() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_with_num_threads(4, 12_000_000)?;
|
||||
let mut index_writer = index.writer(12_000_000).unwrap();
|
||||
// create 8 segments with 100 tiny docs
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "a"))?;
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
}
|
||||
{
|
||||
let mut prepared_commit = index_writer.prepare_commit()?;
|
||||
let mut prepared_commit = index_writer.prepare_commit().expect("commit failed");
|
||||
prepared_commit.set_payload("first commit");
|
||||
prepared_commit.abort()?;
|
||||
prepared_commit.commit().expect("commit failed");
|
||||
}
|
||||
{
|
||||
let metas = index.load_metas()?;
|
||||
let metas = index.load_metas().unwrap();
|
||||
assert_eq!(metas.payload.unwrap(), "first commit");
|
||||
}
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
}
|
||||
index_writer.commit().unwrap();
|
||||
{
|
||||
let metas = index.load_metas().unwrap();
|
||||
assert!(metas.payload.is_none());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_prepare_but_rollback() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_with_num_threads(4, 12_000_000).unwrap();
|
||||
// create 8 segments with 100 tiny docs
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
}
|
||||
{
|
||||
let mut prepared_commit = index_writer.prepare_commit().expect("commit failed");
|
||||
prepared_commit.set_payload("first commit");
|
||||
prepared_commit.abort().expect("commit failed");
|
||||
}
|
||||
{
|
||||
let metas = index.load_metas().unwrap();
|
||||
assert!(metas.payload.is_none());
|
||||
}
|
||||
for _doc in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "b"))?;
|
||||
index_writer.add_document(doc!(text_field => "b"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let num_docs_containing = |s: &str| {
|
||||
let term_a = Term::from_field_text(text_field, s);
|
||||
index
|
||||
.reader_builder()
|
||||
.reload_policy(ReloadPolicy::Manual)
|
||||
.try_into()?
|
||||
.try_into()
|
||||
.unwrap()
|
||||
.searcher()
|
||||
.doc_freq(&term_a)
|
||||
.unwrap()
|
||||
};
|
||||
assert_eq!(num_docs_containing("a")?, 0);
|
||||
assert_eq!(num_docs_containing("b")?, 100);
|
||||
Ok(())
|
||||
assert_eq!(num_docs_containing("a"), 0);
|
||||
assert_eq!(num_docs_containing("b"), 100);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1151,7 +1171,7 @@ mod tests {
|
||||
};
|
||||
let mut index_writer = index.writer_with_num_threads(4, 12_000_000).unwrap();
|
||||
|
||||
let add_tstamp = index_writer.add_document(doc!(text_field => "a")).unwrap();
|
||||
let add_tstamp = index_writer.add_document(doc!(text_field => "a"));
|
||||
let commit_tstamp = index_writer.commit().unwrap();
|
||||
assert!(commit_tstamp > add_tstamp);
|
||||
index_writer.delete_all_documents().unwrap();
|
||||
@@ -1168,7 +1188,7 @@ mod tests {
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_with_num_threads(4, 12_000_000).unwrap();
|
||||
|
||||
let add_tstamp = index_writer.add_document(doc!(text_field => "a")).unwrap();
|
||||
let add_tstamp = index_writer.add_document(doc!(text_field => "a"));
|
||||
|
||||
// commit documents - they are now available
|
||||
let first_commit = index_writer.commit();
|
||||
@@ -1187,7 +1207,7 @@ mod tests {
|
||||
|
||||
// add new documents again
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field => "b")).unwrap();
|
||||
index_writer.add_document(doc!(text_field => "b"));
|
||||
}
|
||||
|
||||
// rollback to last commit, when index was empty
|
||||
@@ -1221,7 +1241,7 @@ mod tests {
|
||||
|
||||
assert!(index_writer.commit().is_ok());
|
||||
// add one simple doc
|
||||
index_writer.add_document(doc!(text_field => "a")).unwrap();
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
let term_a = Term::from_field_text(text_field, "a");
|
||||
@@ -1245,7 +1265,7 @@ mod tests {
|
||||
let mut index_writer = index.writer_with_num_threads(4, 12_000_000).unwrap();
|
||||
|
||||
// add one simple doc
|
||||
assert!(index_writer.add_document(doc!(text_field => "a")).is_ok());
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
let comm = index_writer.commit();
|
||||
assert!(comm.is_ok());
|
||||
let commit_tstamp = comm.unwrap();
|
||||
@@ -1321,13 +1341,13 @@ mod tests {
|
||||
|
||||
// create and delete docs in same commit
|
||||
for id in 0u64..5u64 {
|
||||
index_writer.add_document(doc!(id_field => id))?;
|
||||
index_writer.add_document(doc!(id_field => id));
|
||||
}
|
||||
for id in 2u64..4u64 {
|
||||
index_writer.delete_term(Term::from_field_u64(id_field, id));
|
||||
}
|
||||
for id in 5u64..10u64 {
|
||||
index_writer.add_document(doc!(id_field => id))?;
|
||||
index_writer.add_document(doc!(id_field => id));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_reader.reload()?;
|
||||
@@ -1355,24 +1375,15 @@ mod tests {
|
||||
Merge,
|
||||
}
|
||||
|
||||
fn balanced_operation_strategy() -> impl Strategy<Value = IndexingOp> {
|
||||
fn operation_strategy() -> impl Strategy<Value = IndexingOp> {
|
||||
prop_oneof![
|
||||
(0u64..20u64).prop_map(|id| IndexingOp::DeleteDoc { id }),
|
||||
(0u64..20u64).prop_map(|id| IndexingOp::AddDoc { id }),
|
||||
(0u64..1u64).prop_map(|_| IndexingOp::Commit),
|
||||
(0u64..10u64).prop_map(|id| IndexingOp::DeleteDoc { id }),
|
||||
(0u64..10u64).prop_map(|id| IndexingOp::AddDoc { id }),
|
||||
(0u64..2u64).prop_map(|_| IndexingOp::Commit),
|
||||
(0u64..1u64).prop_map(|_| IndexingOp::Merge),
|
||||
]
|
||||
}
|
||||
|
||||
fn adding_operation_strategy() -> impl Strategy<Value = IndexingOp> {
|
||||
prop_oneof![
|
||||
10 => (0u64..100u64).prop_map(|id| IndexingOp::DeleteDoc { id }),
|
||||
50 => (0u64..100u64).prop_map(|id| IndexingOp::AddDoc { id }),
|
||||
2 => (0u64..1u64).prop_map(|_| IndexingOp::Commit),
|
||||
1 => (0u64..1u64).prop_map(|_| IndexingOp::Merge),
|
||||
]
|
||||
}
|
||||
|
||||
fn expected_ids(ops: &[IndexingOp]) -> (HashMap<u64, u64>, HashSet<u64>) {
|
||||
let mut existing_ids = HashMap::new();
|
||||
let mut deleted_ids = HashSet::new();
|
||||
@@ -1439,14 +1450,12 @@ mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
|
||||
let old_reader = index.reader()?;
|
||||
|
||||
for &op in ops {
|
||||
match op {
|
||||
IndexingOp::AddDoc { id } => {
|
||||
let facet = Facet::from(&("/cola/".to_string() + &id.to_string()));
|
||||
index_writer
|
||||
.add_document(doc!(id_field=>id, multi_numbers=> id, multi_numbers => id, text_field => id.to_string(), facet_field => facet, large_text_field=> LOREM))?;
|
||||
.add_document(doc!(id_field=>id, multi_numbers=> id, multi_numbers => id, text_field => id.to_string(), facet_field => facet, large_text_field=> LOREM));
|
||||
}
|
||||
IndexingOp::DeleteDoc { id } => {
|
||||
index_writer.delete_term(Term::from_field_u64(id_field, id));
|
||||
@@ -1479,21 +1488,6 @@ mod tests {
|
||||
assert!(index_writer.wait_merging_threads().is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
old_reader.reload()?;
|
||||
let old_searcher = old_reader.searcher();
|
||||
|
||||
let ids_old_searcher: HashSet<u64> = old_searcher
|
||||
.segment_readers()
|
||||
.iter()
|
||||
.flat_map(|segment_reader| {
|
||||
let ff_reader = segment_reader.fast_fields().u64(id_field).unwrap();
|
||||
segment_reader
|
||||
.doc_ids_alive()
|
||||
.map(move |doc| ff_reader.get(doc))
|
||||
})
|
||||
.collect();
|
||||
|
||||
let ids: HashSet<u64> = searcher
|
||||
.segment_readers()
|
||||
.iter()
|
||||
@@ -1506,19 +1500,6 @@ mod tests {
|
||||
.collect();
|
||||
|
||||
let (expected_ids_and_num_occurences, deleted_ids) = expected_ids(ops);
|
||||
let num_docs_expected = expected_ids_and_num_occurences
|
||||
.iter()
|
||||
.map(|(_, id_occurences)| *id_occurences as usize)
|
||||
.sum::<usize>();
|
||||
assert_eq!(searcher.num_docs() as usize, num_docs_expected);
|
||||
assert_eq!(old_searcher.num_docs() as usize, num_docs_expected);
|
||||
assert_eq!(
|
||||
ids_old_searcher,
|
||||
expected_ids_and_num_occurences
|
||||
.keys()
|
||||
.cloned()
|
||||
.collect::<HashSet<_>>()
|
||||
);
|
||||
assert_eq!(
|
||||
ids,
|
||||
expected_ids_and_num_occurences
|
||||
@@ -1613,42 +1594,22 @@ mod tests {
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(20))]
|
||||
#[test]
|
||||
fn test_delete_with_sort_proptest_adding(ops in proptest::collection::vec(adding_operation_strategy(), 1..100)) {
|
||||
fn test_delete_with_sort_proptest(ops in proptest::collection::vec(operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], true, false).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_without_sort_proptest_adding(ops in proptest::collection::vec(adding_operation_strategy(), 1..100)) {
|
||||
fn test_delete_without_sort_proptest(ops in proptest::collection::vec(operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], false, false).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_with_sort_proptest_with_merge_adding(ops in proptest::collection::vec(adding_operation_strategy(), 1..100)) {
|
||||
fn test_delete_with_sort_proptest_with_merge(ops in proptest::collection::vec(operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], true, true).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_without_sort_proptest_with_merge_adding(ops in proptest::collection::vec(adding_operation_strategy(), 1..100)) {
|
||||
fn test_delete_without_sort_proptest_with_merge(ops in proptest::collection::vec(operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], false, true).is_ok());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_delete_with_sort_proptest(ops in proptest::collection::vec(balanced_operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], true, false).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_without_sort_proptest(ops in proptest::collection::vec(balanced_operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], false, false).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_with_sort_proptest_with_merge(ops in proptest::collection::vec(balanced_operation_strategy(), 1..10)) {
|
||||
assert!(test_operation_strategy(&ops[..], true, true).is_ok());
|
||||
}
|
||||
#[test]
|
||||
fn test_delete_without_sort_proptest_with_merge(ops in proptest::collection::vec(balanced_operation_strategy(), 1..100)) {
|
||||
assert!(test_operation_strategy(&ops[..], false, true).is_ok());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1673,11 +1634,11 @@ mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
|
||||
// We add a doc...
|
||||
index_writer.add_document(doc!(sort_by_field => 2u64, id_field => 0u64))?;
|
||||
index_writer.add_document(doc!(sort_by_field => 2u64, id_field => 0u64));
|
||||
// And remove it.
|
||||
index_writer.delete_term(Term::from_field_u64(id_field, 0u64));
|
||||
// We add another doc.
|
||||
index_writer.add_document(doc!(sort_by_field=>1u64, id_field => 0u64))?;
|
||||
index_writer.add_document(doc!(sort_by_field=>1u64, id_field => 0u64));
|
||||
|
||||
// The expected result is a segment with
|
||||
// maxdoc = 2
|
||||
@@ -1694,14 +1655,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_index_doc_missing_field() -> crate::Result<()> {
|
||||
fn test_index_doc_missing_field() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let idfield = schema_builder.add_text_field("id", STRING);
|
||||
schema_builder.add_text_field("optfield", STRING);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(idfield=>"myid"))?;
|
||||
index_writer.commit()?;
|
||||
Ok(())
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(idfield=>"myid"));
|
||||
let commit = index_writer.commit();
|
||||
assert!(commit.is_ok());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
use super::AddBatchReceiver;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct IndexWriterStatus {
|
||||
inner: Arc<Inner>,
|
||||
}
|
||||
|
||||
impl IndexWriterStatus {
|
||||
/// Returns true iff the index writer is alive.
|
||||
pub fn is_alive(&self) -> bool {
|
||||
self.inner.as_ref().is_alive()
|
||||
}
|
||||
|
||||
/// Returns a copy of the operation receiver.
|
||||
/// If the index writer was killed, returns None.
|
||||
pub fn operation_receiver(&self) -> Option<AddBatchReceiver> {
|
||||
let rlock = self
|
||||
.inner
|
||||
.receive_channel
|
||||
.read()
|
||||
.expect("This lock should never be poisoned");
|
||||
rlock.as_ref().map(|receiver| receiver.clone())
|
||||
}
|
||||
|
||||
/// Create an index writer bomb.
|
||||
/// If dropped, the index writer status will be killed.
|
||||
pub(crate) fn create_bomb(&self) -> IndexWriterBomb {
|
||||
IndexWriterBomb {
|
||||
inner: Some(self.inner.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct Inner {
|
||||
is_alive: AtomicBool,
|
||||
receive_channel: RwLock<Option<AddBatchReceiver>>,
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
fn is_alive(&self) -> bool {
|
||||
self.is_alive.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn kill(&self) {
|
||||
self.is_alive.store(false, Ordering::Relaxed);
|
||||
self.receive_channel
|
||||
.write()
|
||||
.expect("This lock should never be poisoned")
|
||||
.take();
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AddBatchReceiver> for IndexWriterStatus {
|
||||
fn from(receiver: AddBatchReceiver) -> Self {
|
||||
IndexWriterStatus {
|
||||
inner: Arc::new(Inner {
|
||||
is_alive: AtomicBool::new(true),
|
||||
receive_channel: RwLock::new(Some(receiver)),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// If dropped, the index writer will be killed.
|
||||
/// To prevent this, clients can call `.defuse()`.
|
||||
pub(crate) struct IndexWriterBomb {
|
||||
inner: Option<Arc<Inner>>,
|
||||
}
|
||||
|
||||
impl IndexWriterBomb {
|
||||
/// Defuses the bomb.
|
||||
///
|
||||
/// This is the only way to drop the bomb without killing
|
||||
/// the index writer.
|
||||
pub fn defuse(mut self) {
|
||||
self.inner = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for IndexWriterBomb {
|
||||
fn drop(&mut self) {
|
||||
if let Some(inner) = self.inner.take() {
|
||||
inner.kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::IndexWriterStatus;
|
||||
use crossbeam::channel;
|
||||
use std::mem;
|
||||
|
||||
#[test]
|
||||
fn test_bomb_goes_boom() {
|
||||
let (_tx, rx) = channel::bounded(10);
|
||||
let index_writer_status: IndexWriterStatus = IndexWriterStatus::from(rx);
|
||||
assert!(index_writer_status.operation_receiver().is_some());
|
||||
let bomb = index_writer_status.create_bomb();
|
||||
assert!(index_writer_status.operation_receiver().is_some());
|
||||
mem::drop(bomb);
|
||||
// boom!
|
||||
assert!(index_writer_status.operation_receiver().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bomb_defused() {
|
||||
let (_tx, rx) = channel::bounded(10);
|
||||
let index_writer_status: IndexWriterStatus = IndexWriterStatus::from(rx);
|
||||
assert!(index_writer_status.operation_receiver().is_some());
|
||||
let bomb = index_writer_status.create_bomb();
|
||||
bomb.defuse();
|
||||
assert!(index_writer_status.operation_receiver().is_some());
|
||||
}
|
||||
}
|
||||
@@ -114,7 +114,7 @@ mod tests {
|
||||
use crate::Index;
|
||||
|
||||
#[test]
|
||||
fn create_index_test_max_merge_issue_1035() -> crate::Result<()> {
|
||||
fn create_index_test_max_merge_issue_1035() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let int_field = schema_builder.add_u64_field("intval", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
@@ -127,34 +127,34 @@ mod tests {
|
||||
log_merge_policy.set_max_docs_before_merge(1);
|
||||
log_merge_policy.set_min_layer_size(0);
|
||||
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.set_merge_policy(Box::new(log_merge_policy));
|
||||
|
||||
// after every commit the merge checker is started, it will merge only segments with 1
|
||||
// element in it because of the max_merge_size.
|
||||
index_writer.add_document(doc!(int_field=>1_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>1_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>2_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>2_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>3_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>3_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>4_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>4_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>5_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>5_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>6_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>6_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>7_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>7_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
|
||||
index_writer.add_document(doc!(int_field=>8_u64))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>8_u64));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
let _segment_ids = index
|
||||
@@ -169,7 +169,6 @@ mod tests {
|
||||
panic!("segment can't have more than two segments");
|
||||
} // don't know how to wait for the merge, then it could be a simple eq
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn test_merge_policy() -> LogMergePolicy {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::error::DataCorruption;
|
||||
use crate::fastfield::AliveBitSet;
|
||||
use crate::fastfield::CompositeFastFieldSerializer;
|
||||
use crate::fastfield::DynamicFastFieldReader;
|
||||
use crate::fastfield::FastFieldDataAccess;
|
||||
@@ -10,7 +9,7 @@ use crate::fastfield::MultiValuedFastFieldReader;
|
||||
use crate::fieldnorm::FieldNormsSerializer;
|
||||
use crate::fieldnorm::FieldNormsWriter;
|
||||
use crate::fieldnorm::{FieldNormReader, FieldNormReaders};
|
||||
use crate::indexer::doc_id_mapping::SegmentDocIdMapping;
|
||||
use crate::indexer::doc_id_mapping::SegmentDocidMapping;
|
||||
use crate::indexer::SegmentSerializer;
|
||||
use crate::postings::Postings;
|
||||
use crate::postings::{InvertedIndexSerializer, SegmentPostings};
|
||||
@@ -157,38 +156,16 @@ impl IndexMerger {
|
||||
schema: Schema,
|
||||
index_settings: IndexSettings,
|
||||
segments: &[Segment],
|
||||
) -> crate::Result<IndexMerger> {
|
||||
let delete_bitsets = segments.iter().map(|_| None).collect_vec();
|
||||
Self::open_with_custom_alive_set(schema, index_settings, segments, delete_bitsets)
|
||||
}
|
||||
|
||||
// Create merge with a custom delete set.
|
||||
// For every Segment, a delete bitset can be provided, which
|
||||
// will be merged with the existing bit set. Make sure the index
|
||||
// corresponds to the segment index.
|
||||
//
|
||||
// If `None` is provided for custom alive set, the regular alive set will be used.
|
||||
// If a delete_bitsets is provided, the union between the provided and regular
|
||||
// alive set will be used.
|
||||
//
|
||||
// This can be used to merge but also apply an additional filter.
|
||||
// One use case is demux, which is basically taking a list of
|
||||
// segments and partitions them e.g. by a value in a field.
|
||||
pub fn open_with_custom_alive_set(
|
||||
schema: Schema,
|
||||
index_settings: IndexSettings,
|
||||
segments: &[Segment],
|
||||
alive_bitset_opt: Vec<Option<AliveBitSet>>,
|
||||
) -> crate::Result<IndexMerger> {
|
||||
let mut readers = vec![];
|
||||
for (segment, new_alive_bitset_opt) in segments.iter().zip(alive_bitset_opt.into_iter()) {
|
||||
let mut max_doc: u32 = 0u32;
|
||||
for segment in segments {
|
||||
if segment.meta().num_docs() > 0 {
|
||||
let reader =
|
||||
SegmentReader::open_with_custom_alive_set(segment, new_alive_bitset_opt)?;
|
||||
let reader = SegmentReader::open(segment)?;
|
||||
max_doc += reader.num_docs();
|
||||
readers.push(reader);
|
||||
}
|
||||
}
|
||||
let max_doc = readers.iter().map(|reader| reader.num_docs()).sum();
|
||||
if let Some(sort_by_field) = index_settings.sort_by_field.as_ref() {
|
||||
readers = Self::sort_readers_by_min_sort_field(readers, sort_by_field)?;
|
||||
}
|
||||
@@ -236,7 +213,7 @@ impl IndexMerger {
|
||||
fn write_fieldnorms(
|
||||
&self,
|
||||
mut fieldnorms_serializer: FieldNormsSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
let fields = FieldNormsWriter::fields_with_fieldnorm(&self.schema);
|
||||
let mut fieldnorms_data = Vec::with_capacity(self.max_doc as usize);
|
||||
@@ -264,7 +241,7 @@ impl IndexMerger {
|
||||
&self,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
mut term_ord_mappings: HashMap<Field, TermOrdinalMapping>,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
debug_time!("write_fast_fields");
|
||||
|
||||
@@ -315,7 +292,7 @@ impl IndexMerger {
|
||||
&self,
|
||||
field: Field,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
let (min_value, max_value) = self.readers.iter().map(|reader|{
|
||||
let u64_reader: DynamicFastFieldReader<u64> = reader
|
||||
@@ -347,17 +324,17 @@ impl IndexMerger {
|
||||
num_vals: doc_id_mapping.len() as u64,
|
||||
};
|
||||
#[derive(Clone)]
|
||||
struct SortedDocIdFieldAccessProvider<'a> {
|
||||
doc_id_mapping: &'a SegmentDocIdMapping,
|
||||
struct SortedDocidFieldAccessProvider<'a> {
|
||||
doc_id_mapping: &'a SegmentDocidMapping,
|
||||
fast_field_readers: &'a Vec<DynamicFastFieldReader<u64>>,
|
||||
}
|
||||
impl<'a> FastFieldDataAccess for SortedDocIdFieldAccessProvider<'a> {
|
||||
impl<'a> FastFieldDataAccess for SortedDocidFieldAccessProvider<'a> {
|
||||
fn get_val(&self, doc: u64) -> u64 {
|
||||
let (doc_id, reader_ordinal) = self.doc_id_mapping[doc as usize];
|
||||
self.fast_field_readers[reader_ordinal as usize].get(doc_id)
|
||||
}
|
||||
}
|
||||
let fastfield_accessor = SortedDocIdFieldAccessProvider {
|
||||
let fastfield_accessor = SortedDocidFieldAccessProvider {
|
||||
doc_id_mapping,
|
||||
fast_field_readers: &fast_field_readers,
|
||||
};
|
||||
@@ -412,9 +389,9 @@ impl IndexMerger {
|
||||
Ok(value_accessor)
|
||||
}
|
||||
/// Collecting value_accessors into a vec to bind the lifetime.
|
||||
pub(crate) fn get_reader_with_sort_field_accessor(
|
||||
&self,
|
||||
sort_by_field: &IndexSortByField,
|
||||
pub(crate) fn get_reader_with_sort_field_accessor<'a, 'b>(
|
||||
&'a self,
|
||||
sort_by_field: &'b IndexSortByField,
|
||||
) -> crate::Result<Vec<(SegmentOrdinal, impl FastFieldReader<u64> + Clone)>> {
|
||||
let reader_ordinal_and_field_accessors = self
|
||||
.readers
|
||||
@@ -439,7 +416,7 @@ impl IndexMerger {
|
||||
pub(crate) fn generate_doc_id_mapping(
|
||||
&self,
|
||||
sort_by_field: &IndexSortByField,
|
||||
) -> crate::Result<SegmentDocIdMapping> {
|
||||
) -> crate::Result<SegmentDocidMapping> {
|
||||
let reader_ordinal_and_field_accessors =
|
||||
self.get_reader_with_sort_field_accessor(sort_by_field)?;
|
||||
// Loading the field accessor on demand causes a 15x regression
|
||||
@@ -482,7 +459,7 @@ impl IndexMerger {
|
||||
})
|
||||
.map(|(doc_id, reader_with_id, _)| (doc_id, reader_with_id)),
|
||||
);
|
||||
Ok(SegmentDocIdMapping::new(sorted_doc_ids, false))
|
||||
Ok(SegmentDocidMapping::new(sorted_doc_ids, false))
|
||||
}
|
||||
|
||||
// Creating the index file to point into the data, generic over `BytesFastFieldReader` and
|
||||
@@ -491,7 +468,7 @@ impl IndexMerger {
|
||||
fn write_1_n_fast_field_idx_generic<T: MultiValueLength>(
|
||||
field: Field,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
reader_and_field_accessors: &[(&SegmentReader, T)],
|
||||
) -> crate::Result<Vec<u64>> {
|
||||
let mut total_num_vals = 0u64;
|
||||
@@ -550,7 +527,7 @@ impl IndexMerger {
|
||||
&self,
|
||||
field: Field,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<Vec<u64>> {
|
||||
let reader_ordinal_and_field_accessors = self.readers.iter().map(|reader|{
|
||||
let u64s_reader: MultiValuedFastFieldReader<u64> = reader.fast_fields()
|
||||
@@ -572,7 +549,7 @@ impl IndexMerger {
|
||||
field: Field,
|
||||
term_ordinal_mappings: &TermOrdinalMapping,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
debug_time!("write_hierarchical_facet_field");
|
||||
|
||||
@@ -621,7 +598,7 @@ impl IndexMerger {
|
||||
|
||||
/// Creates a mapping if the segments are stacked. this is helpful to merge codelines between index
|
||||
/// sorting and the others
|
||||
pub(crate) fn get_doc_id_from_concatenated_data(&self) -> crate::Result<SegmentDocIdMapping> {
|
||||
pub(crate) fn get_doc_id_from_concatenated_data(&self) -> crate::Result<SegmentDocidMapping> {
|
||||
let total_num_new_docs = self
|
||||
.readers
|
||||
.iter()
|
||||
@@ -641,13 +618,13 @@ impl IndexMerger {
|
||||
})
|
||||
.flatten(),
|
||||
);
|
||||
Ok(SegmentDocIdMapping::new(mapping, true))
|
||||
Ok(SegmentDocidMapping::new(mapping, true))
|
||||
}
|
||||
fn write_multi_fast_field(
|
||||
&self,
|
||||
field: Field,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
// Multifastfield consists in 2 fastfields.
|
||||
// The first serves as an index into the second one and is stricly increasing.
|
||||
@@ -703,16 +680,16 @@ impl IndexMerger {
|
||||
min_value,
|
||||
};
|
||||
|
||||
struct SortedDocIdMultiValueAccessProvider<'a> {
|
||||
doc_id_mapping: &'a SegmentDocIdMapping,
|
||||
struct SortedDocidMultiValueAccessProvider<'a> {
|
||||
doc_id_mapping: &'a SegmentDocidMapping,
|
||||
fast_field_readers: &'a Vec<MultiValuedFastFieldReader<u64>>,
|
||||
offsets: Vec<u64>,
|
||||
}
|
||||
impl<'a> FastFieldDataAccess for SortedDocIdMultiValueAccessProvider<'a> {
|
||||
impl<'a> FastFieldDataAccess for SortedDocidMultiValueAccessProvider<'a> {
|
||||
fn get_val(&self, pos: u64) -> u64 {
|
||||
// use the offsets index to find the doc_id which will contain the position.
|
||||
// the offsets are stricly increasing so we can do a simple search on it.
|
||||
let new_doc_id = self
|
||||
let new_docid = self
|
||||
.offsets
|
||||
.iter()
|
||||
.position(|&offset| offset > pos)
|
||||
@@ -720,10 +697,10 @@ impl IndexMerger {
|
||||
- 1;
|
||||
|
||||
// now we need to find the position of `pos` in the multivalued bucket
|
||||
let num_pos_covered_until_now = self.offsets[new_doc_id];
|
||||
let num_pos_covered_until_now = self.offsets[new_docid];
|
||||
let pos_in_values = pos - num_pos_covered_until_now;
|
||||
|
||||
let (old_doc_id, reader_ordinal) = self.doc_id_mapping[new_doc_id as usize];
|
||||
let (old_doc_id, reader_ordinal) = self.doc_id_mapping[new_docid as usize];
|
||||
let num_vals = self.fast_field_readers[reader_ordinal as usize].get_len(old_doc_id);
|
||||
assert!(num_vals >= pos_in_values);
|
||||
let mut vals = vec![];
|
||||
@@ -732,7 +709,7 @@ impl IndexMerger {
|
||||
vals[pos_in_values as usize]
|
||||
}
|
||||
}
|
||||
let fastfield_accessor = SortedDocIdMultiValueAccessProvider {
|
||||
let fastfield_accessor = SortedDocidMultiValueAccessProvider {
|
||||
doc_id_mapping,
|
||||
fast_field_readers: &ff_readers,
|
||||
offsets,
|
||||
@@ -771,7 +748,7 @@ impl IndexMerger {
|
||||
&self,
|
||||
field: Field,
|
||||
fast_field_serializer: &mut CompositeFastFieldSerializer,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
let reader_and_field_accessors = self
|
||||
.readers
|
||||
@@ -807,7 +784,7 @@ impl IndexMerger {
|
||||
field_type: &FieldType,
|
||||
serializer: &mut InvertedIndexSerializer,
|
||||
fieldnorm_reader: Option<FieldNormReader>,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<Option<TermOrdinalMapping>> {
|
||||
debug_time!("write_postings_for_field");
|
||||
let mut positions_buffer: Vec<u32> = Vec::with_capacity(1_000);
|
||||
@@ -846,8 +823,8 @@ impl IndexMerger {
|
||||
segment_local_map
|
||||
})
|
||||
.collect();
|
||||
for (new_doc_id, (old_doc_id, segment_ord)) in doc_id_mapping.iter().enumerate() {
|
||||
let segment_map = &mut merged_doc_id_map[*segment_ord as usize];
|
||||
for (new_doc_id, (old_doc_id, segment_ordinal)) in doc_id_mapping.iter().enumerate() {
|
||||
let segment_map = &mut merged_doc_id_map[*segment_ordinal as usize];
|
||||
segment_map[*old_doc_id as usize] = Some(new_doc_id as DocId);
|
||||
}
|
||||
|
||||
@@ -889,7 +866,7 @@ impl IndexMerger {
|
||||
let mut total_doc_freq = 0;
|
||||
|
||||
// Let's compute the list of non-empty posting lists
|
||||
for (segment_ord, term_info) in merged_terms.current_segment_ords_and_term_infos() {
|
||||
for (segment_ord, term_info) in merged_terms.current_segment_ordinals_and_term_infos() {
|
||||
let segment_reader = &self.readers[segment_ord];
|
||||
let inverted_index: &InvertedIndexReader = &*field_readers[segment_ord];
|
||||
let segment_postings = inverted_index
|
||||
@@ -939,9 +916,9 @@ impl IndexMerger {
|
||||
// there is at least one document.
|
||||
let term_freq = segment_postings.term_freq();
|
||||
segment_postings.positions(&mut positions_buffer);
|
||||
// if doc_id_mapping exists, the doc_ids are reordered, they are
|
||||
// if doc_id_mapping exists, the docids are reordered, they are
|
||||
// not just stacked. The field serializer expects monotonically increasing
|
||||
// doc_ids, so we collect and sort them first, before writing.
|
||||
// docids, so we collect and sort them first, before writing.
|
||||
//
|
||||
// I think this is not strictly necessary, it would be possible to
|
||||
// avoid the loading into a vec via some form of kmerge, but then the merge
|
||||
@@ -981,7 +958,7 @@ impl IndexMerger {
|
||||
&self,
|
||||
serializer: &mut InvertedIndexSerializer,
|
||||
fieldnorm_readers: FieldNormReaders,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<HashMap<Field, TermOrdinalMapping>> {
|
||||
let mut term_ordinal_mappings = HashMap::new();
|
||||
for (field, field_entry) in self.schema.fields() {
|
||||
@@ -1004,7 +981,7 @@ impl IndexMerger {
|
||||
fn write_storable_fields(
|
||||
&self,
|
||||
store_writer: &mut StoreWriter,
|
||||
doc_id_mapping: &SegmentDocIdMapping,
|
||||
doc_id_mapping: &SegmentDocidMapping,
|
||||
) -> crate::Result<()> {
|
||||
debug_time!("write_storable_fields");
|
||||
|
||||
@@ -1046,7 +1023,7 @@ impl IndexMerger {
|
||||
// the doc stores would be on average half full, given total randomness (which
|
||||
// is not the case here, but not sure how it behaves exactly).
|
||||
//
|
||||
// https://github.com/quickwit-inc/tantivy/issues/1053
|
||||
// https://github.com/tantivy-search/tantivy/issues/1053
|
||||
//
|
||||
// take 7 in order to not walk over all checkpoints.
|
||||
|| store_reader.block_checkpoints().take(7).count() < 6
|
||||
@@ -1162,17 +1139,18 @@ mod tests {
|
||||
score_field => 3u64,
|
||||
date_field => curr_time,
|
||||
bytes_score_field => 3u32.to_be_bytes().as_ref()
|
||||
))?;
|
||||
));
|
||||
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "a b c",
|
||||
score_field => 5u64,
|
||||
bytes_score_field => 5u32.to_be_bytes().as_ref()
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "a b c d",
|
||||
score_field => 7u64,
|
||||
bytes_score_field => 7u32.to_be_bytes().as_ref()
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
// writing the segment
|
||||
index_writer.add_document(doc!(
|
||||
@@ -1180,12 +1158,12 @@ mod tests {
|
||||
date_field => curr_time,
|
||||
score_field => 11u64,
|
||||
bytes_score_field => 11u32.to_be_bytes().as_ref()
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "a b c g",
|
||||
score_field => 13u64,
|
||||
bytes_score_field => 13u32.to_be_bytes().as_ref()
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
}
|
||||
{
|
||||
@@ -1319,18 +1297,18 @@ mod tests {
|
||||
text_field => "a b d",
|
||||
score_field => 1u64,
|
||||
bytes_score_field => vec![0u8, 0, 0, 1],
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "b c",
|
||||
score_field => 2u64,
|
||||
bytes_score_field => vec![0u8, 0, 0, 2],
|
||||
))?;
|
||||
));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "c"));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "c d",
|
||||
score_field => 3u64,
|
||||
bytes_score_field => vec![0u8, 0, 0, 3],
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -1360,24 +1338,24 @@ mod tests {
|
||||
text_field => "a d e",
|
||||
score_field => 4_000u64,
|
||||
bytes_score_field => vec![0u8, 0, 0, 4],
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "e f",
|
||||
score_field => 5_000u64,
|
||||
bytes_score_field => vec![0u8, 0, 0, 5],
|
||||
))?;
|
||||
));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "a"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "f"));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "f g",
|
||||
score_field => 6_000u64,
|
||||
bytes_score_field => vec![0u8, 0, 23, 112],
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
text_field => "g h",
|
||||
score_field => 7_000u64,
|
||||
bytes_score_field => vec![0u8, 0, 27, 88],
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -1595,7 +1573,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_merge_facets_sort_asc() {
|
||||
// In the merge case this will go through the doc_id mapping code
|
||||
// In the merge case this will go through the docid mapping code
|
||||
test_merge_facets(
|
||||
Some(IndexSettings {
|
||||
sort_by_field: Some(IndexSortByField {
|
||||
@@ -1606,7 +1584,7 @@ mod tests {
|
||||
}),
|
||||
true,
|
||||
);
|
||||
// In the merge case this will not go through the doc_id mapping code, because the data is
|
||||
// In the merge case this will not go through the docid mapping code, because the data is
|
||||
// sorted and disjunct
|
||||
test_merge_facets(
|
||||
Some(IndexSettings {
|
||||
@@ -1622,7 +1600,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_merge_facets_sort_desc() {
|
||||
// In the merge case this will go through the doc_id mapping code
|
||||
// In the merge case this will go through the docid mapping code
|
||||
test_merge_facets(
|
||||
Some(IndexSettings {
|
||||
sort_by_field: Some(IndexSortByField {
|
||||
@@ -1633,7 +1611,7 @@ mod tests {
|
||||
}),
|
||||
true,
|
||||
);
|
||||
// In the merge case this will not go through the doc_id mapping code, because the data is
|
||||
// In the merge case this will not go through the docid mapping code, because the data is
|
||||
// sorted and disjunct
|
||||
test_merge_facets(
|
||||
Some(IndexSettings {
|
||||
@@ -1673,7 +1651,7 @@ mod tests {
|
||||
}
|
||||
doc.add_u64(int_field, *int_val);
|
||||
*int_val += 1;
|
||||
index_writer.add_document(doc).unwrap();
|
||||
index_writer.add_document(doc);
|
||||
};
|
||||
|
||||
index_doc(
|
||||
@@ -1786,69 +1764,70 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bug_merge() -> crate::Result<()> {
|
||||
fn test_bug_merge() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let int_field = schema_builder.add_u64_field("intvals", INDEXED);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(int_field => 1u64))?;
|
||||
index_writer.add_document(doc!(int_field => 1u64));
|
||||
index_writer.commit().expect("commit failed");
|
||||
index_writer.add_document(doc!(int_field => 1u64))?;
|
||||
index_writer.add_document(doc!(int_field => 1u64));
|
||||
index_writer.commit().expect("commit failed");
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
assert_eq!(searcher.num_docs(), 2);
|
||||
index_writer.delete_term(Term::from_field_u64(int_field, 1));
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
reader.reload()?;
|
||||
block_on(index_writer.merge(&segment_ids)).expect("Merging failed");
|
||||
reader.reload().unwrap();
|
||||
// commit has not been called yet. The document should still be
|
||||
// there.
|
||||
assert_eq!(reader.searcher().num_docs(), 2);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_multivalued_int_fields_all_deleted() -> crate::Result<()> {
|
||||
fn test_merge_multivalued_int_fields_all_deleted() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let int_options = IntOptions::default()
|
||||
.set_fast(Cardinality::MultiValues)
|
||||
.set_indexed();
|
||||
let int_field = schema_builder.add_u64_field("intvals", int_options);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let mut doc = Document::default();
|
||||
doc.add_u64(int_field, 1);
|
||||
index_writer.add_document(doc.clone())?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc.clone());
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc);
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.delete_term(Term::from_field_u64(int_field, 1));
|
||||
let segment_ids = index.searchable_segment_ids()?;
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||
|
||||
// assert delete has not been committed
|
||||
reader.reload()?;
|
||||
assert!(reader.reload().is_ok());
|
||||
let searcher = reader.searcher();
|
||||
assert_eq!(searcher.num_docs(), 2);
|
||||
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
|
||||
index_writer.wait_merging_threads()?;
|
||||
index_writer.wait_merging_threads().unwrap();
|
||||
}
|
||||
|
||||
reader.reload()?;
|
||||
reader.reload().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
assert_eq!(searcher.num_docs(), 0);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_multivalued_int_fields_simple() -> crate::Result<()> {
|
||||
fn test_merge_multivalued_int_fields_simple() {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let int_options = IntOptions::default()
|
||||
.set_fast(Cardinality::MultiValues)
|
||||
@@ -1857,13 +1836,13 @@ mod tests {
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let index_doc = |index_writer: &mut IndexWriter, int_vals: &[u64]| {
|
||||
let mut doc = Document::default();
|
||||
for &val in int_vals {
|
||||
doc.add_u64(int_field, val);
|
||||
}
|
||||
index_writer.add_document(doc).unwrap();
|
||||
index_writer.add_document(doc);
|
||||
};
|
||||
index_doc(&mut index_writer, &[1, 2]);
|
||||
index_doc(&mut index_writer, &[1, 2, 3]);
|
||||
@@ -1879,7 +1858,7 @@ mod tests {
|
||||
index_doc(&mut index_writer, &[1_000]);
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let mut vals: Vec<u64> = Vec::new();
|
||||
|
||||
@@ -1928,12 +1907,14 @@ mod tests {
|
||||
|
||||
// Merging the segments
|
||||
{
|
||||
let segment_ids = index.searchable_segment_ids()?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
index_writer.wait_merging_threads()?;
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||
assert!(index_writer.wait_merging_threads().is_ok());
|
||||
}
|
||||
reader.reload()?;
|
||||
assert!(reader.reload().is_ok());
|
||||
|
||||
{
|
||||
let searcher = reader.searcher();
|
||||
@@ -1970,7 +1951,6 @@ mod tests {
|
||||
ff_reader.get_vals(9, &mut vals);
|
||||
assert_eq!(&vals, &[20]);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1996,7 +1976,7 @@ mod tests {
|
||||
doc.add_f64(field, 42.0);
|
||||
doc.add_f64(multi_field, 0.24);
|
||||
doc.add_f64(multi_field, 0.27);
|
||||
writer.add_document(doc)?;
|
||||
writer.add_document(doc);
|
||||
if i % 5 == 0 {
|
||||
writer.commit()?;
|
||||
}
|
||||
@@ -2020,7 +2000,7 @@ mod tests {
|
||||
let happy_term = Term::from_field_text(text, "happy");
|
||||
let term_query = TermQuery::new(happy_term, IndexRecordOption::WithFreqs);
|
||||
for _ in 0..62 {
|
||||
writer.add_document(doc!(text=>"hello happy tax payer"))?;
|
||||
writer.add_document(doc!(text=>"hello happy tax payer"));
|
||||
}
|
||||
writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
@@ -2032,7 +2012,7 @@ mod tests {
|
||||
assert_nearly_equals!(term_scorer.block_max_score(), 0.0079681855);
|
||||
assert_nearly_equals!(term_scorer.score(), 0.0079681855);
|
||||
for _ in 0..81 {
|
||||
writer.add_document(doc!(text=>"hello happy tax payer"))?;
|
||||
writer.add_document(doc!(text=>"hello happy tax payer"));
|
||||
}
|
||||
writer.commit()?;
|
||||
reader.reload()?;
|
||||
|
||||
@@ -39,17 +39,14 @@ mod tests {
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(int_field=>3_u64, facet_field=> Facet::from("/crime")))
|
||||
.unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(int_field=>6_u64, facet_field=> Facet::from("/crime")))
|
||||
.unwrap();
|
||||
index_writer.commit().unwrap();
|
||||
index_writer
|
||||
.add_document(doc!(int_field=>5_u64, facet_field=> Facet::from("/fanta")))
|
||||
.unwrap();
|
||||
index_writer.commit().unwrap();
|
||||
|
||||
index_writer.add_document(doc!(int_field=>3_u64, facet_field=> Facet::from("/crime")));
|
||||
index_writer.add_document(doc!(int_field=>6_u64, facet_field=> Facet::from("/crime")));
|
||||
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc!(int_field=>5_u64, facet_field=> Facet::from("/fanta")));
|
||||
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
// Merging the segments
|
||||
@@ -69,7 +66,7 @@ mod tests {
|
||||
fn create_test_index(
|
||||
index_settings: Option<IndexSettings>,
|
||||
force_disjunct_segment_sort_values: bool,
|
||||
) -> crate::Result<Index> {
|
||||
) -> Index {
|
||||
let mut schema_builder = schema::Schema::builder();
|
||||
let int_options = IntOptions::default()
|
||||
.set_fast(Cardinality::SingleValue)
|
||||
@@ -98,34 +95,34 @@ mod tests {
|
||||
if let Some(settings) = index_settings {
|
||||
index_builder = index_builder.settings(settings);
|
||||
}
|
||||
let index = index_builder.create_in_ram()?;
|
||||
let index = index_builder.create_in_ram().unwrap();
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
|
||||
// segment 1 - range 1-3
|
||||
index_writer.add_document(doc!(int_field=>1_u64))?;
|
||||
index_writer.add_document(doc!(int_field=>1_u64));
|
||||
index_writer.add_document(
|
||||
doc!(int_field=>3_u64, multi_numbers => 3_u64, multi_numbers => 4_u64, bytes_field => vec![1, 2, 3], text_field => "some text", facet_field=> Facet::from("/book/crime")),
|
||||
)?;
|
||||
);
|
||||
index_writer.add_document(
|
||||
doc!(int_field=>1_u64, text_field=> "deleteme", text_field => "ok text more text"),
|
||||
)?;
|
||||
);
|
||||
index_writer.add_document(
|
||||
doc!(int_field=>2_u64, multi_numbers => 2_u64, multi_numbers => 3_u64, text_field => "ok text more text"),
|
||||
)?;
|
||||
);
|
||||
|
||||
index_writer.commit()?;
|
||||
assert!(index_writer.commit().is_ok());
|
||||
// segment 2 - range 1-20 , with force_disjunct_segment_sort_values 10-20
|
||||
index_writer.add_document(doc!(int_field=>20_u64, multi_numbers => 20_u64))?;
|
||||
index_writer.add_document(doc!(int_field=>20_u64, multi_numbers => 20_u64));
|
||||
|
||||
let in_val = if force_disjunct_segment_sort_values {
|
||||
10_u64
|
||||
} else {
|
||||
1
|
||||
};
|
||||
index_writer.add_document(doc!(int_field=>in_val, text_field=> "deleteme" , text_field => "ok text more text", facet_field=> Facet::from("/book/crime")))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(int_field=>in_val, text_field=> "deleteme" , text_field => "ok text more text", facet_field=> Facet::from("/book/crime")));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
// segment 3 - range 5-1000, with force_disjunct_segment_sort_values 50-1000
|
||||
let int_vals = if force_disjunct_segment_sort_values {
|
||||
[100_u64, 50]
|
||||
@@ -134,24 +131,26 @@ mod tests {
|
||||
};
|
||||
index_writer.add_document( // position of this doc after delete in desc sorting = [2], in disjunct case [1]
|
||||
doc!(int_field=>int_vals[0], multi_numbers => 10_u64, multi_numbers => 11_u64, text_field=> "blubber", facet_field=> Facet::from("/book/fantasy")),
|
||||
)?;
|
||||
index_writer.add_document(doc!(int_field=>int_vals[1], text_field=> "deleteme"))?;
|
||||
);
|
||||
index_writer.add_document(doc!(int_field=>int_vals[1], text_field=> "deleteme"));
|
||||
index_writer.add_document(
|
||||
doc!(int_field=>1_000u64, multi_numbers => 1001_u64, multi_numbers => 1002_u64, bytes_field => vec![5, 5],text_field => "the biggest num")
|
||||
)?;
|
||||
);
|
||||
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "deleteme"));
|
||||
index_writer.commit()?;
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
// Merging the segments
|
||||
{
|
||||
let segment_ids = index.searchable_segment_ids()?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
index_writer.wait_merging_threads()?;
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||
assert!(index_writer.wait_merging_threads().is_ok());
|
||||
}
|
||||
Ok(index)
|
||||
index
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -184,8 +183,7 @@ mod tests {
|
||||
..Default::default()
|
||||
}),
|
||||
force_disjunct_segment_sort_values,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let int_field = index.schema().get_field("intval").unwrap();
|
||||
let reader = index.reader().unwrap();
|
||||
@@ -302,8 +300,7 @@ mod tests {
|
||||
..Default::default()
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
@@ -370,8 +367,7 @@ mod tests {
|
||||
..Default::default()
|
||||
}),
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
);
|
||||
|
||||
let int_field = index.schema().get_field("intval").unwrap();
|
||||
let multi_numbers = index.schema().get_field("multi_numbers").unwrap();
|
||||
@@ -558,7 +554,7 @@ mod bench_sorted_index_merge {
|
||||
.expect("Failed to find a reader for single fast field. This is a tantivy bug and it should never happen.");
|
||||
(doc_id, reader, u64_reader)
|
||||
});
|
||||
// add values in order of the new doc_ids
|
||||
// add values in order of the new docids
|
||||
let mut val = 0;
|
||||
for (doc_id, _reader, field_reader) in sorted_doc_ids {
|
||||
val = field_reader.get(*doc_id);
|
||||
@@ -571,7 +567,7 @@ mod bench_sorted_index_merge {
|
||||
Ok(())
|
||||
}
|
||||
#[bench]
|
||||
fn create_sorted_index_create_doc_id_mapping(b: &mut Bencher) -> crate::Result<()> {
|
||||
fn create_sorted_index_create_docid_mapping(b: &mut Bencher) -> crate::Result<()> {
|
||||
let sort_by_field = IndexSortByField {
|
||||
field: "intval".to_string(),
|
||||
order: Order::Desc,
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
pub mod delete_queue;
|
||||
|
||||
pub mod demuxer;
|
||||
pub mod doc_id_mapping;
|
||||
mod doc_opstamp_mapping;
|
||||
pub mod index_writer;
|
||||
mod index_writer_status;
|
||||
mod log_merge_policy;
|
||||
mod merge_operation;
|
||||
pub mod merge_policy;
|
||||
pub mod merger;
|
||||
mod merger_sorted_index_test;
|
||||
pub mod operation;
|
||||
pub mod prepared_commit;
|
||||
mod prepared_commit;
|
||||
mod segment_entry;
|
||||
mod segment_manager;
|
||||
mod segment_register;
|
||||
@@ -20,11 +18,6 @@ pub mod segment_updater;
|
||||
mod segment_writer;
|
||||
mod stamper;
|
||||
|
||||
use crossbeam::channel;
|
||||
use smallvec::SmallVec;
|
||||
|
||||
use crate::indexer::operation::AddOperation;
|
||||
|
||||
pub use self::index_writer::IndexWriter;
|
||||
pub use self::log_merge_policy::LogMergePolicy;
|
||||
pub use self::merge_operation::MergeOperation;
|
||||
@@ -33,23 +26,12 @@ pub use self::prepared_commit::PreparedCommit;
|
||||
pub use self::segment_entry::SegmentEntry;
|
||||
pub use self::segment_manager::SegmentManager;
|
||||
pub use self::segment_serializer::SegmentSerializer;
|
||||
pub use self::segment_updater::merge_filtered_segments;
|
||||
pub use self::segment_updater::merge_indices;
|
||||
pub use self::segment_updater::merge_segments;
|
||||
pub use self::segment_writer::SegmentWriter;
|
||||
|
||||
/// Alias for the default merge policy, which is the `LogMergePolicy`.
|
||||
pub type DefaultMergePolicy = LogMergePolicy;
|
||||
|
||||
// Batch of documents.
|
||||
// Most of the time, users will send operation one-by-one, but it can be useful to
|
||||
// send them as a small block to ensure that
|
||||
// - all docs in the operation will happen on the same segment and continuous doc_ids.
|
||||
// - all operations in the group are committed at the same time, making the group
|
||||
// atomic.
|
||||
type AddBatch = SmallVec<[AddOperation; 4]>;
|
||||
type AddBatchSender = channel::Sender<AddBatch>;
|
||||
type AddBatchReceiver = channel::Receiver<AddBatch>;
|
||||
|
||||
#[cfg(feature = "mmap")]
|
||||
#[cfg(test)]
|
||||
mod tests_mmap {
|
||||
@@ -57,20 +39,19 @@ mod tests_mmap {
|
||||
use crate::{Index, Term};
|
||||
|
||||
#[test]
|
||||
fn test_advance_delete_bug() -> crate::Result<()> {
|
||||
fn test_advance_delete_bug() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", schema::TEXT);
|
||||
let index = Index::create_from_tempdir(schema_builder.build())?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let index = Index::create_from_tempdir(schema_builder.build()).unwrap();
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
// there must be one deleted document in the segment
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "b"));
|
||||
// we need enough data to trigger the bug (at least 32 documents)
|
||||
for _ in 0..32 {
|
||||
index_writer.add_document(doc!(text_field=>"c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"c"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_writer.commit()?;
|
||||
Ok(())
|
||||
index_writer.commit().unwrap();
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,38 +18,25 @@ impl<'a> PreparedCommit<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the opstamp associated to the prepared commit.
|
||||
pub fn opstamp(&self) -> Opstamp {
|
||||
self.opstamp
|
||||
}
|
||||
|
||||
/// Adds an arbitrary payload to the commit.
|
||||
pub fn set_payload(&mut self, payload: &str) {
|
||||
self.payload = Some(payload.to_string())
|
||||
}
|
||||
|
||||
/// Rollbacks any change.
|
||||
pub fn abort(self) -> crate::Result<Opstamp> {
|
||||
self.index_writer.rollback()
|
||||
}
|
||||
|
||||
/// Proceeds to commit.
|
||||
/// See `.commit_async()`.
|
||||
pub fn commit(self) -> crate::Result<Opstamp> {
|
||||
block_on(self.commit_async())
|
||||
}
|
||||
|
||||
/// Proceeds to commit.
|
||||
///
|
||||
/// Unfortunately, contrary to what `PrepareCommit` may suggests,
|
||||
/// this operation is not at all really light.
|
||||
/// At this point deletes have not been flushed yet.
|
||||
pub async fn commit_async(self) -> crate::Result<Opstamp> {
|
||||
info!("committing {}", self.opstamp);
|
||||
self.index_writer
|
||||
.segment_updater()
|
||||
.schedule_commit(self.opstamp, self.payload)
|
||||
.await?;
|
||||
let _ = block_on(
|
||||
self.index_writer
|
||||
.segment_updater()
|
||||
.schedule_commit(self.opstamp, self.payload),
|
||||
);
|
||||
Ok(self.opstamp)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,10 +66,13 @@ impl SegmentRegister {
|
||||
}
|
||||
|
||||
pub fn segment_metas(&self) -> Vec<SegmentMeta> {
|
||||
self.segment_states
|
||||
let mut segment_ids: Vec<SegmentMeta> = self
|
||||
.segment_states
|
||||
.values()
|
||||
.map(|segment_entry| segment_entry.meta().clone())
|
||||
.collect()
|
||||
.collect();
|
||||
segment_ids.sort_by_key(SegmentMeta::id);
|
||||
segment_ids
|
||||
}
|
||||
|
||||
pub fn contains_all(&self, segment_ids: &[SegmentId]) -> bool {
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::core::SegmentId;
|
||||
use crate::core::SegmentMeta;
|
||||
use crate::core::META_FILEPATH;
|
||||
use crate::directory::{Directory, DirectoryClone, GarbageCollectionResult};
|
||||
use crate::fastfield::AliveBitSet;
|
||||
use crate::indexer::delete_queue::DeleteCursor;
|
||||
use crate::indexer::index_writer::advance_deletes;
|
||||
use crate::indexer::merge_operation::MergeOperationInventory;
|
||||
@@ -20,15 +19,12 @@ use crate::indexer::{DefaultMergePolicy, MergePolicy};
|
||||
use crate::indexer::{MergeCandidate, MergeOperation};
|
||||
use crate::schema::Schema;
|
||||
use crate::Opstamp;
|
||||
use crate::TantivyError;
|
||||
use fail::fail_point;
|
||||
use futures::channel::oneshot;
|
||||
use futures::executor::{ThreadPool, ThreadPoolBuilder};
|
||||
use futures::future::Future;
|
||||
use futures::future::TryFutureExt;
|
||||
use std::borrow::BorrowMut;
|
||||
use std::collections::HashSet;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
use std::ops::Deref;
|
||||
use std::path::PathBuf;
|
||||
@@ -78,10 +74,6 @@ fn save_metas(metas: &IndexMeta, directory: &dyn Directory) -> crate::Result<()>
|
||||
let mut buffer = serde_json::to_vec_pretty(metas)?;
|
||||
// Just adding a new line at the end of the buffer.
|
||||
writeln!(&mut buffer)?;
|
||||
fail_point!("save_metas", |msg| Err(TantivyError::from(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
msg.unwrap_or_else(|| "Undefined".to_string())
|
||||
))));
|
||||
directory.atomic_write(&META_FILEPATH, &buffer[..])?;
|
||||
debug!("Saved metas {:?}", serde_json::to_string_pretty(&metas));
|
||||
Ok(())
|
||||
@@ -167,9 +159,9 @@ fn merge(
|
||||
/// meant to work if you have an IndexWriter running for the origin indices, or
|
||||
/// the destination Index.
|
||||
#[doc(hidden)]
|
||||
pub fn merge_indices<T: Into<Box<dyn Directory>>>(
|
||||
pub fn merge_segments<Dir: Directory>(
|
||||
indices: &[Index],
|
||||
output_directory: T,
|
||||
output_directory: Dir,
|
||||
) -> crate::Result<Index> {
|
||||
if indices.is_empty() {
|
||||
// If there are no indices to merge, there is no need to do anything.
|
||||
@@ -178,8 +170,19 @@ pub fn merge_indices<T: Into<Box<dyn Directory>>>(
|
||||
));
|
||||
}
|
||||
|
||||
let target_schema = indices[0].schema();
|
||||
let target_settings = indices[0].settings().clone();
|
||||
|
||||
// let's check that all of the indices have the same schema
|
||||
if indices
|
||||
.iter()
|
||||
.skip(1)
|
||||
.any(|index| index.schema() != target_schema)
|
||||
{
|
||||
return Err(crate::TantivyError::InvalidArgument(
|
||||
"Attempt to merge different schema indices".to_string(),
|
||||
));
|
||||
}
|
||||
// let's check that all of the indices have the same index settings
|
||||
if indices
|
||||
.iter()
|
||||
@@ -196,61 +199,13 @@ pub fn merge_indices<T: Into<Box<dyn Directory>>>(
|
||||
segments.extend(index.searchable_segments()?);
|
||||
}
|
||||
|
||||
let non_filter = segments.iter().map(|_| None).collect::<Vec<_>>();
|
||||
merge_filtered_segments(&segments, target_settings, non_filter, output_directory)
|
||||
}
|
||||
|
||||
/// Advanced: Merges a list of segments from different indices in a new index.
|
||||
/// Additional you can provide a delete bitset for each segment to ignore doc_ids.
|
||||
///
|
||||
/// Returns `TantivyError` if the the indices list is empty or their
|
||||
/// schemas don't match.
|
||||
///
|
||||
/// `output_directory`: is assumed to be empty.
|
||||
///
|
||||
/// # Warning
|
||||
/// This function does NOT check or take the `IndexWriter` is running. It is not
|
||||
/// meant to work if you have an IndexWriter running for the origin indices, or
|
||||
/// the destination Index.
|
||||
#[doc(hidden)]
|
||||
pub fn merge_filtered_segments<T: Into<Box<dyn Directory>>>(
|
||||
segments: &[Segment],
|
||||
target_settings: IndexSettings,
|
||||
filter_doc_ids: Vec<Option<AliveBitSet>>,
|
||||
output_directory: T,
|
||||
) -> crate::Result<Index> {
|
||||
if segments.is_empty() {
|
||||
// If there are no indices to merge, there is no need to do anything.
|
||||
return Err(crate::TantivyError::InvalidArgument(
|
||||
"No segments given to marge".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let target_schema = segments[0].schema();
|
||||
|
||||
// let's check that all of the indices have the same schema
|
||||
if segments
|
||||
.iter()
|
||||
.skip(1)
|
||||
.any(|index| index.schema() != target_schema)
|
||||
{
|
||||
return Err(crate::TantivyError::InvalidArgument(
|
||||
"Attempt to merge different schema indices".to_string(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut merged_index = Index::create(
|
||||
output_directory,
|
||||
target_schema.clone(),
|
||||
target_settings.clone(),
|
||||
)?;
|
||||
let mut merged_index = Index::create(output_directory, target_schema.clone(), target_settings)?;
|
||||
let merged_segment = merged_index.new_segment();
|
||||
let merged_segment_id = merged_segment.id();
|
||||
let merger: IndexMerger = IndexMerger::open_with_custom_alive_set(
|
||||
let merger: IndexMerger = IndexMerger::open(
|
||||
merged_index.schema(),
|
||||
merged_index.settings().clone(),
|
||||
segments,
|
||||
filter_doc_ids,
|
||||
&segments[..],
|
||||
)?;
|
||||
let segment_serializer = SegmentSerializer::for_segment(merged_segment, true)?;
|
||||
let num_docs = merger.write(segment_serializer)?;
|
||||
@@ -270,7 +225,7 @@ pub fn merge_filtered_segments<T: Into<Box<dyn Directory>>>(
|
||||
);
|
||||
|
||||
let index_meta = IndexMeta {
|
||||
index_settings: target_settings, // index_settings of all segments should be the same
|
||||
index_settings: indices[0].load_metas()?.index_settings, // index_settings of all segments should be the same
|
||||
segments: vec![segment_meta],
|
||||
schema: target_schema,
|
||||
opstamp: 0u64,
|
||||
@@ -351,39 +306,37 @@ impl SegmentUpdater {
|
||||
*self.merge_policy.write().unwrap() = arc_merge_policy;
|
||||
}
|
||||
|
||||
async fn schedule_task<
|
||||
T: 'static + Send,
|
||||
F: Future<Output = crate::Result<T>> + 'static + Send,
|
||||
>(
|
||||
fn schedule_future<T: 'static + Send, F: Future<Output = crate::Result<T>> + 'static + Send>(
|
||||
&self,
|
||||
task: F,
|
||||
) -> crate::Result<T> {
|
||||
if !self.is_alive() {
|
||||
return Err(crate::TantivyError::SystemError(
|
||||
"Segment updater killed".to_string(),
|
||||
));
|
||||
}
|
||||
f: F,
|
||||
) -> impl Future<Output = crate::Result<T>> {
|
||||
let (sender, receiver) = oneshot::channel();
|
||||
self.pool.spawn_ok(async move {
|
||||
let task_result = task.await;
|
||||
let _ = sender.send(task_result);
|
||||
});
|
||||
let task_result = receiver.await;
|
||||
task_result.unwrap_or_else(|_| {
|
||||
if self.is_alive() {
|
||||
self.pool.spawn_ok(async move {
|
||||
let _ = sender.send(f.await);
|
||||
});
|
||||
} else {
|
||||
let _ = sender.send(Err(crate::TantivyError::SystemError(
|
||||
"Segment updater killed".to_string(),
|
||||
)));
|
||||
}
|
||||
receiver.unwrap_or_else(|_| {
|
||||
let err_msg =
|
||||
"A segment_updater future did not success. This should never happen.".to_string();
|
||||
Err(crate::TantivyError::SystemError(err_msg))
|
||||
})
|
||||
}
|
||||
|
||||
pub async fn schedule_add_segment(&self, segment_entry: SegmentEntry) -> crate::Result<()> {
|
||||
pub fn schedule_add_segment(
|
||||
&self,
|
||||
segment_entry: SegmentEntry,
|
||||
) -> impl Future<Output = crate::Result<()>> {
|
||||
let segment_updater = self.clone();
|
||||
self.schedule_task(async move {
|
||||
self.schedule_future(async move {
|
||||
segment_updater.segment_manager.add_segment(segment_entry);
|
||||
segment_updater.consider_merge_options().await;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
/// Orders `SegmentManager` to remove all segments
|
||||
@@ -450,9 +403,11 @@ impl SegmentUpdater {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn schedule_garbage_collect(&self) -> crate::Result<GarbageCollectionResult> {
|
||||
pub fn schedule_garbage_collect(
|
||||
&self,
|
||||
) -> impl Future<Output = crate::Result<GarbageCollectionResult>> {
|
||||
let garbage_collect_future = garbage_collect_files(self.clone());
|
||||
self.schedule_task(garbage_collect_future).await
|
||||
self.schedule_future(garbage_collect_future)
|
||||
}
|
||||
|
||||
/// List the files that are useful to the index.
|
||||
@@ -470,13 +425,13 @@ impl SegmentUpdater {
|
||||
files
|
||||
}
|
||||
|
||||
pub(crate) async fn schedule_commit(
|
||||
pub fn schedule_commit(
|
||||
&self,
|
||||
opstamp: Opstamp,
|
||||
payload: Option<String>,
|
||||
) -> crate::Result<()> {
|
||||
) -> impl Future<Output = crate::Result<()>> {
|
||||
let segment_updater: SegmentUpdater = self.clone();
|
||||
self.schedule_task(async move {
|
||||
self.schedule_future(async move {
|
||||
let segment_entries = segment_updater.purge_deletes(opstamp)?;
|
||||
segment_updater.segment_manager.commit(segment_entries);
|
||||
segment_updater.save_metas(opstamp, payload)?;
|
||||
@@ -484,7 +439,6 @@ impl SegmentUpdater {
|
||||
segment_updater.consider_merge_options().await;
|
||||
Ok(())
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
fn store_meta(&self, index_meta: &IndexMeta) {
|
||||
@@ -559,7 +513,9 @@ impl SegmentUpdater {
|
||||
e
|
||||
);
|
||||
// ... cancel merge
|
||||
assert!(!cfg!(test), "Merge failed.");
|
||||
if cfg!(test) {
|
||||
panic!("Merge failed.");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -612,14 +568,14 @@ impl SegmentUpdater {
|
||||
}
|
||||
}
|
||||
|
||||
async fn end_merge(
|
||||
fn end_merge(
|
||||
&self,
|
||||
merge_operation: MergeOperation,
|
||||
mut after_merge_segment_entry: SegmentEntry,
|
||||
) -> crate::Result<SegmentMeta> {
|
||||
) -> impl Future<Output = crate::Result<SegmentMeta>> {
|
||||
let segment_updater = self.clone();
|
||||
let after_merge_segment_meta = after_merge_segment_entry.meta().clone();
|
||||
self.schedule_task(async move {
|
||||
let end_merge_future = self.schedule_future(async move {
|
||||
info!("End merge {:?}", after_merge_segment_entry.meta());
|
||||
{
|
||||
let mut delete_cursor = after_merge_segment_entry.delete_cursor().clone();
|
||||
@@ -638,8 +594,9 @@ impl SegmentUpdater {
|
||||
merge_operation.segment_ids(),
|
||||
advance_deletes_err
|
||||
);
|
||||
assert!(!cfg!(test), "Merge failed.");
|
||||
|
||||
if cfg!(test) {
|
||||
panic!("Merge failed.");
|
||||
}
|
||||
// ... cancel merge
|
||||
// `merge_operations` are tracked. As it is dropped, the
|
||||
// the segment_ids will be available again for merge.
|
||||
@@ -662,9 +619,8 @@ impl SegmentUpdater {
|
||||
|
||||
let _ = garbage_collect_files(segment_updater).await;
|
||||
Ok(())
|
||||
})
|
||||
.await?;
|
||||
Ok(after_merge_segment_meta)
|
||||
});
|
||||
end_merge_future.map_ok(|_| after_merge_segment_meta)
|
||||
}
|
||||
|
||||
/// Wait for current merging threads.
|
||||
@@ -690,19 +646,11 @@ impl SegmentUpdater {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::merge_indices;
|
||||
use crate::collector::TopDocs;
|
||||
use super::merge_segments;
|
||||
use crate::directory::RamDirectory;
|
||||
use crate::fastfield::AliveBitSet;
|
||||
use crate::indexer::merge_policy::tests::MergeWheneverPossible;
|
||||
use crate::indexer::merger::IndexMerger;
|
||||
use crate::indexer::segment_updater::merge_filtered_segments;
|
||||
use crate::query::QueryParser;
|
||||
use crate::schema::*;
|
||||
use crate::Directory;
|
||||
use crate::DocAddress;
|
||||
use crate::Index;
|
||||
use crate::Segment;
|
||||
|
||||
#[test]
|
||||
fn test_delete_during_merge() -> crate::Result<()> {
|
||||
@@ -715,19 +663,19 @@ mod tests {
|
||||
index_writer.set_merge_policy(Box::new(MergeWheneverPossible));
|
||||
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"d"))?;
|
||||
index_writer.add_document(doc!(text_field=>"c"));
|
||||
index_writer.add_document(doc!(text_field=>"d"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
index_writer.add_document(doc!(text_field=>"e"))?;
|
||||
index_writer.add_document(doc!(text_field=>"f"))?;
|
||||
index_writer.add_document(doc!(text_field=>"e"));
|
||||
index_writer.add_document(doc!(text_field=>"f"));
|
||||
index_writer.commit()?;
|
||||
|
||||
let term = Term::from_field_text(text_field, "a");
|
||||
@@ -745,50 +693,6 @@ mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_all_docs_min() -> crate::Result<()> {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
|
||||
for _ in 0..10 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
let seg_ids = index.searchable_segment_ids()?;
|
||||
// docs exist, should have at least 1 segment
|
||||
assert!(!seg_ids.is_empty());
|
||||
|
||||
let term = Term::from_field_text(text_field, "a");
|
||||
index_writer.delete_term(term);
|
||||
index_writer.commit()?;
|
||||
|
||||
let term = Term::from_field_text(text_field, "b");
|
||||
index_writer.delete_term(term);
|
||||
index_writer.commit()?;
|
||||
|
||||
index_writer.wait_merging_threads()?;
|
||||
|
||||
let reader = index.reader()?;
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
|
||||
let seg_ids = index.searchable_segment_ids()?;
|
||||
assert!(seg_ids.is_empty());
|
||||
|
||||
reader.reload()?;
|
||||
assert_eq!(reader.searcher().num_docs(), 0);
|
||||
// empty segments should be erased
|
||||
assert!(index.searchable_segment_metas()?.is_empty());
|
||||
assert!(reader.searcher().segment_readers().is_empty());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_all_docs() -> crate::Result<()> {
|
||||
let mut schema_builder = Schema::builder();
|
||||
@@ -799,19 +703,19 @@ mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"d"))?;
|
||||
index_writer.add_document(doc!(text_field=>"c"));
|
||||
index_writer.add_document(doc!(text_field=>"d"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
index_writer.add_document(doc!(text_field=>"e"))?;
|
||||
index_writer.add_document(doc!(text_field=>"f"))?;
|
||||
index_writer.add_document(doc!(text_field=>"e"));
|
||||
index_writer.add_document(doc!(text_field=>"f"));
|
||||
index_writer.commit()?;
|
||||
|
||||
let seg_ids = index.searchable_segment_ids()?;
|
||||
@@ -851,8 +755,8 @@ mod tests {
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
@@ -878,22 +782,22 @@ mod tests {
|
||||
// writing two segments
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
for _ in 0..100 {
|
||||
index_writer.add_document(doc!(text_field=>"fizz"))?;
|
||||
index_writer.add_document(doc!(text_field=>"buzz"))?;
|
||||
index_writer.add_document(doc!(text_field=>"fizz"));
|
||||
index_writer.add_document(doc!(text_field=>"buzz"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
for _ in 0..1000 {
|
||||
index_writer.add_document(doc!(text_field=>"foo"))?;
|
||||
index_writer.add_document(doc!(text_field=>"bar"))?;
|
||||
index_writer.add_document(doc!(text_field=>"foo"));
|
||||
index_writer.add_document(doc!(text_field=>"bar"));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
indices.push(index);
|
||||
}
|
||||
|
||||
assert_eq!(indices.len(), 3);
|
||||
let output_directory: Box<dyn Directory> = Box::new(RamDirectory::default());
|
||||
let index = merge_indices(&indices, output_directory)?;
|
||||
let output_directory = RamDirectory::default();
|
||||
let index = merge_segments(&indices, output_directory)?;
|
||||
assert_eq!(index.schema(), schema);
|
||||
|
||||
let segments = index.searchable_segments()?;
|
||||
@@ -907,7 +811,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_merge_empty_indices_array() {
|
||||
let merge_result = merge_indices(&[], RamDirectory::default());
|
||||
let merge_result = merge_segments(&[], RamDirectory::default());
|
||||
assert!(merge_result.is_err());
|
||||
}
|
||||
|
||||
@@ -918,7 +822,7 @@ mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"some text"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text"));
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
@@ -928,197 +832,15 @@ mod tests {
|
||||
let body_field = schema_builder.add_text_field("body", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(body_field=>"some body"))?;
|
||||
index_writer.add_document(doc!(body_field=>"some body"));
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
// mismatched schema index list
|
||||
let result = merge_indices(&[first_index, second_index], RamDirectory::default());
|
||||
let result = merge_segments(&[first_index, second_index], RamDirectory::default());
|
||||
assert!(result.is_err());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_filtered_segments() -> crate::Result<()> {
|
||||
let first_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 1"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 2"))?;
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let second_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 3"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 4"))?;
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "4"));
|
||||
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let mut segments: Vec<Segment> = Vec::new();
|
||||
segments.extend(first_index.searchable_segments()?);
|
||||
segments.extend(second_index.searchable_segments()?);
|
||||
|
||||
let target_settings = first_index.settings().clone();
|
||||
|
||||
let filter_segment_1 = AliveBitSet::for_test_from_deleted_docs(&[1], 2);
|
||||
let filter_segment_2 = AliveBitSet::for_test_from_deleted_docs(&[0], 2);
|
||||
|
||||
let filter_segments = vec![Some(filter_segment_1), Some(filter_segment_2)];
|
||||
|
||||
let merged_index = merge_filtered_segments(
|
||||
&segments,
|
||||
target_settings,
|
||||
filter_segments,
|
||||
RamDirectory::default(),
|
||||
)?;
|
||||
|
||||
let segments = merged_index.searchable_segments()?;
|
||||
assert_eq!(segments.len(), 1);
|
||||
|
||||
let segment_metas = segments[0].meta();
|
||||
assert_eq!(segment_metas.num_deleted_docs(), 0);
|
||||
assert_eq!(segment_metas.num_docs(), 1);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_merge_single_filtered_segments() -> crate::Result<()> {
|
||||
let first_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"test text"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 2"))?;
|
||||
|
||||
index_writer.add_document(doc!(text_field=>"some text 3"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 4"))?;
|
||||
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "4"));
|
||||
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let mut segments: Vec<Segment> = Vec::new();
|
||||
segments.extend(first_index.searchable_segments()?);
|
||||
|
||||
let target_settings = first_index.settings().clone();
|
||||
|
||||
let filter_segment = AliveBitSet::for_test_from_deleted_docs(&[0], 4);
|
||||
|
||||
let filter_segments = vec![Some(filter_segment)];
|
||||
|
||||
let index = merge_filtered_segments(
|
||||
&segments,
|
||||
target_settings,
|
||||
filter_segments,
|
||||
RamDirectory::default(),
|
||||
)?;
|
||||
|
||||
let segments = index.searchable_segments()?;
|
||||
assert_eq!(segments.len(), 1);
|
||||
|
||||
let segment_metas = segments[0].meta();
|
||||
assert_eq!(segment_metas.num_deleted_docs(), 0);
|
||||
assert_eq!(segment_metas.num_docs(), 2);
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
{
|
||||
let text_field = index.schema().get_field("text").unwrap();
|
||||
|
||||
let do_search = |term: &str| {
|
||||
let query = QueryParser::for_index(&index, vec![text_field])
|
||||
.parse_query(term)
|
||||
.unwrap();
|
||||
let top_docs: Vec<(f32, DocAddress)> =
|
||||
searcher.search(&query, &TopDocs::with_limit(3)).unwrap();
|
||||
|
||||
top_docs.iter().map(|el| el.1.doc_id).collect::<Vec<_>>()
|
||||
};
|
||||
|
||||
assert_eq!(do_search("test"), vec![] as Vec<u32>);
|
||||
assert_eq!(do_search("text"), vec![0, 1]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_apply_doc_id_filter_in_merger() -> crate::Result<()> {
|
||||
let first_index = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 1"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 2"))?;
|
||||
|
||||
index_writer.add_document(doc!(text_field=>"some text 3"))?;
|
||||
index_writer.add_document(doc!(text_field=>"some text 4"))?;
|
||||
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "4"));
|
||||
|
||||
index_writer.commit()?;
|
||||
index
|
||||
};
|
||||
|
||||
let mut segments: Vec<Segment> = Vec::new();
|
||||
segments.extend(first_index.searchable_segments()?);
|
||||
|
||||
let target_settings = first_index.settings().clone();
|
||||
{
|
||||
let filter_segment = AliveBitSet::for_test_from_deleted_docs(&[1], 4);
|
||||
let filter_segments = vec![Some(filter_segment)];
|
||||
let target_schema = segments[0].schema();
|
||||
let merged_index = Index::create(
|
||||
RamDirectory::default(),
|
||||
target_schema.clone(),
|
||||
target_settings.clone(),
|
||||
)?;
|
||||
let merger: IndexMerger = IndexMerger::open_with_custom_alive_set(
|
||||
merged_index.schema(),
|
||||
merged_index.settings().clone(),
|
||||
&segments[..],
|
||||
filter_segments,
|
||||
)?;
|
||||
|
||||
let doc_ids_alive: Vec<_> = merger.readers[0].doc_ids_alive().collect();
|
||||
assert_eq!(doc_ids_alive, vec![0, 2]);
|
||||
}
|
||||
|
||||
{
|
||||
let filter_segments = vec![None];
|
||||
let target_schema = segments[0].schema();
|
||||
let merged_index = Index::create(
|
||||
RamDirectory::default(),
|
||||
target_schema.clone(),
|
||||
target_settings.clone(),
|
||||
)?;
|
||||
let merger: IndexMerger = IndexMerger::open_with_custom_alive_set(
|
||||
merged_index.schema(),
|
||||
merged_index.settings().clone(),
|
||||
&segments[..],
|
||||
filter_segments,
|
||||
)?;
|
||||
|
||||
let doc_ids_alive: Vec<_> = merger.readers[0].doc_ids_alive().collect();
|
||||
assert_eq!(doc_ids_alive, vec![0, 1, 2]);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
125
src/lib.rs
125
src/lib.rs
@@ -10,7 +10,8 @@
|
||||
)]
|
||||
#![doc(test(attr(allow(unused_variables), deny(warnings))))]
|
||||
#![warn(missing_docs)]
|
||||
#![allow(clippy::len_without_is_empty)]
|
||||
|
||||
#![feature(async_closure)]
|
||||
|
||||
//! # `tantivy`
|
||||
//!
|
||||
@@ -63,7 +64,7 @@
|
||||
//! body => "He was an old man who fished alone in a skiff in \
|
||||
//! the Gulf Stream and he had gone eighty-four days \
|
||||
//! now without taking a fish."
|
||||
//! ))?;
|
||||
//! ));
|
||||
//!
|
||||
//! // We need to call .commit() explicitly to force the
|
||||
//! // index_writer to finish processing the documents in the queue,
|
||||
@@ -104,7 +105,7 @@
|
||||
//! A good place for you to get started is to check out
|
||||
//! the example code (
|
||||
//! [literate programming](https://tantivy-search.github.io/examples/basic_search.html) /
|
||||
//! [source code](https://github.com/quickwit-inc/tantivy/blob/main/examples/basic_search.rs))
|
||||
//! [source code](https://github.com/tantivy-search/tantivy/blob/main/examples/basic_search.rs))
|
||||
|
||||
#[cfg_attr(test, macro_use)]
|
||||
extern crate serde_json;
|
||||
@@ -127,6 +128,8 @@ mod macros;
|
||||
pub use crate::error::TantivyError;
|
||||
pub use chrono;
|
||||
|
||||
pub const PKG_JS: &'static str = "./pkg/pool_exec.js"; // path to `wasm-bindgen`'s JS binding
|
||||
|
||||
/// Tantivy result.
|
||||
///
|
||||
/// Within tantivy, please avoid importing `Result` using `use crate::Result`
|
||||
@@ -170,11 +173,9 @@ pub use crate::core::{
|
||||
};
|
||||
pub use crate::core::{InvertedIndexReader, SegmentReader};
|
||||
pub use crate::directory::Directory;
|
||||
pub use crate::indexer::demuxer::*;
|
||||
pub use crate::indexer::merge_filtered_segments;
|
||||
pub use crate::indexer::merge_indices;
|
||||
pub use crate::indexer::merge_segments;
|
||||
pub use crate::indexer::operation::UserOperation;
|
||||
pub use crate::indexer::{IndexWriter, PreparedCommit};
|
||||
pub use crate::indexer::IndexWriter;
|
||||
pub use crate::postings::Postings;
|
||||
pub use crate::reader::LeasedItem;
|
||||
pub use crate::schema::{Document, Term};
|
||||
@@ -379,22 +380,24 @@ pub mod tests {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_from_tempdir(schema)?;
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let index = Index::create_from_tempdir(schema).unwrap();
|
||||
{
|
||||
let doc = doc!(text_field=>"af b");
|
||||
index_writer.add_document(doc)?;
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
{
|
||||
let doc = doc!(text_field=>"af b");
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
{
|
||||
let doc = doc!(text_field=>"a b c");
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
{
|
||||
let doc = doc!(text_field=>"a b c d");
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
{
|
||||
let doc = doc!(text_field=>"a b c");
|
||||
index_writer.add_document(doc)?;
|
||||
}
|
||||
{
|
||||
let doc = doc!(text_field=>"a b c d");
|
||||
index_writer.add_document(doc)?;
|
||||
}
|
||||
index_writer.commit()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -404,12 +407,12 @@ pub mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"a a"));
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=>"c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"c"));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -431,7 +434,7 @@ pub mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
index_writer.commit()?;
|
||||
let index_reader = index.reader()?;
|
||||
let searcher = index_reader.searcher();
|
||||
@@ -453,9 +456,9 @@ pub mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let index = Index::create_in_ram(schema_builder.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.add_document(doc!())?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
index_writer.add_document(doc!());
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -497,20 +500,20 @@ pub mod tests {
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
// 0
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
// 1
|
||||
index_writer.add_document(doc!(text_field=>" a c"))?;
|
||||
index_writer.add_document(doc!(text_field=>" a c"));
|
||||
// 2
|
||||
index_writer.add_document(doc!(text_field=>" b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>" b c"));
|
||||
// 3
|
||||
index_writer.add_document(doc!(text_field=>" b d"))?;
|
||||
index_writer.add_document(doc!(text_field=>" b d"));
|
||||
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "c"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "a"));
|
||||
// 4
|
||||
index_writer.add_document(doc!(text_field=>" b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>" b c"));
|
||||
// 5
|
||||
index_writer.add_document(doc!(text_field=>" a"))?;
|
||||
index_writer.add_document(doc!(text_field=>" a"));
|
||||
index_writer.commit()?;
|
||||
}
|
||||
{
|
||||
@@ -544,7 +547,7 @@ pub mod tests {
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
// 0
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
// 1
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "c"));
|
||||
index_writer.rollback()?;
|
||||
@@ -580,7 +583,7 @@ pub mod tests {
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "c"));
|
||||
index_writer.rollback()?;
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "a"));
|
||||
@@ -630,7 +633,7 @@ pub mod tests {
|
||||
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(field=>1u64))?;
|
||||
index_writer.add_document(doc!(field=>1u64));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -654,7 +657,7 @@ pub mod tests {
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let negative_val = -1i64;
|
||||
index_writer.add_document(doc!(value_field => negative_val))?;
|
||||
index_writer.add_document(doc!(value_field => negative_val));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -678,7 +681,7 @@ pub mod tests {
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let val = std::f64::consts::PI;
|
||||
index_writer.add_document(doc!(value_field => val))?;
|
||||
index_writer.add_document(doc!(value_field => val));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -701,7 +704,7 @@ pub mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
@@ -724,14 +727,14 @@ pub mod tests {
|
||||
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"63"))?;
|
||||
index_writer.add_document(doc!(text_field=>"70"))?;
|
||||
index_writer.add_document(doc!(text_field=>"34"))?;
|
||||
index_writer.add_document(doc!(text_field=>"1"))?;
|
||||
index_writer.add_document(doc!(text_field=>"38"))?;
|
||||
index_writer.add_document(doc!(text_field=>"33"))?;
|
||||
index_writer.add_document(doc!(text_field=>"40"))?;
|
||||
index_writer.add_document(doc!(text_field=>"17"))?;
|
||||
index_writer.add_document(doc!(text_field=>"63"));
|
||||
index_writer.add_document(doc!(text_field=>"70"));
|
||||
index_writer.add_document(doc!(text_field=>"34"));
|
||||
index_writer.add_document(doc!(text_field=>"1"));
|
||||
index_writer.add_document(doc!(text_field=>"38"));
|
||||
index_writer.add_document(doc!(text_field=>"33"));
|
||||
index_writer.add_document(doc!(text_field=>"40"));
|
||||
index_writer.add_document(doc!(text_field=>"17"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "38"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "34"));
|
||||
index_writer.commit()?;
|
||||
@@ -749,7 +752,7 @@ pub mod tests {
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"af af af bc bc"))?;
|
||||
index_writer.add_document(doc!(text_field=>"af af af bc bc"));
|
||||
index_writer.commit()?;
|
||||
}
|
||||
{
|
||||
@@ -781,9 +784,9 @@ pub mod tests {
|
||||
let reader = index.reader()?;
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"af af af b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c d"))?;
|
||||
index_writer.add_document(doc!(text_field=>"af af af b"));
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
index_writer.add_document(doc!(text_field=>"a b c d"));
|
||||
index_writer.commit()?;
|
||||
|
||||
reader.reload()?;
|
||||
@@ -845,9 +848,9 @@ pub mod tests {
|
||||
assert_eq!(reader.searcher().num_docs(), 0u64);
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"af b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c d"))?;
|
||||
index_writer.add_document(doc!(text_field=>"af b"));
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
index_writer.add_document(doc!(text_field=>"a b c d"));
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
assert_eq!(reader.searcher().num_docs(), 3u64);
|
||||
@@ -887,7 +890,7 @@ pub mod tests {
|
||||
{
|
||||
let document =
|
||||
doc!(fast_field_unsigned => 4u64, fast_field_signed=>4i64, fast_field_float=>4f64);
|
||||
index_writer.add_document(document)?;
|
||||
index_writer.add_document(document);
|
||||
index_writer.commit()?;
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
@@ -954,7 +957,7 @@ pub mod tests {
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
|
||||
for doc_id in 0u64..DOC_COUNT {
|
||||
index_writer.add_document(doc!(id => doc_id))?;
|
||||
index_writer.add_document(doc!(id => doc_id));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
|
||||
@@ -971,7 +974,7 @@ pub mod tests {
|
||||
index_writer.delete_term(Term::from_field_u64(id, doc_id));
|
||||
index_writer.commit()?;
|
||||
index_reader.reload()?;
|
||||
index_writer.add_document(doc!(id => doc_id))?;
|
||||
index_writer.add_document(doc!(id => doc_id));
|
||||
index_writer.commit()?;
|
||||
index_reader.reload()?;
|
||||
let searcher = index_reader.searcher();
|
||||
@@ -1006,8 +1009,8 @@ pub mod tests {
|
||||
let index = Index::create_in_dir(&index_path, schema)?;
|
||||
let mut writer = index.writer(50_000_000)?;
|
||||
for _ in 0..5000 {
|
||||
writer.add_document(doc!(body => "foo"))?;
|
||||
writer.add_document(doc!(body => "boo"))?;
|
||||
writer.add_document(doc!(body => "foo"));
|
||||
writer.add_document(doc!(body => "boo"));
|
||||
}
|
||||
writer.commit()?;
|
||||
assert!(index.validate_checksum()?.is_empty());
|
||||
|
||||
@@ -1,5 +1,14 @@
|
||||
use crate::postings::compression::COMPRESSION_BLOCK_SIZE;
|
||||
|
||||
unsafe fn binary_search_step(ptr: *const u32, target: u32, half_size: isize) -> *const u32 {
|
||||
let mid = ptr.offset(half_size);
|
||||
if *mid < target {
|
||||
mid.offset(1)
|
||||
} else {
|
||||
ptr
|
||||
}
|
||||
}
|
||||
|
||||
/// Search the first index containing an element greater or equal to
|
||||
/// the target.
|
||||
///
|
||||
@@ -21,16 +30,18 @@ use crate::postings::compression::COMPRESSION_BLOCK_SIZE;
|
||||
/// end of the last block for instance.
|
||||
/// - The target is assumed smaller or equal to the last element of the block.
|
||||
pub fn branchless_binary_search(arr: &[u32; COMPRESSION_BLOCK_SIZE], target: u32) -> usize {
|
||||
let mut start = 0;
|
||||
let mut len = arr.len();
|
||||
for _ in 0..7 {
|
||||
len /= 2;
|
||||
let pivot = unsafe { *arr.get_unchecked(start + len - 1) };
|
||||
if pivot < target {
|
||||
start += len;
|
||||
}
|
||||
let start_ptr: *const u32 = &arr[0] as *const u32;
|
||||
unsafe {
|
||||
let mut ptr = start_ptr;
|
||||
ptr = binary_search_step(ptr, target, 63);
|
||||
ptr = binary_search_step(ptr, target, 31);
|
||||
ptr = binary_search_step(ptr, target, 15);
|
||||
ptr = binary_search_step(ptr, target, 7);
|
||||
ptr = binary_search_step(ptr, target, 3);
|
||||
ptr = binary_search_step(ptr, target, 1);
|
||||
let extra = if *ptr < target { 1 } else { 0 };
|
||||
(ptr.offset_from(start_ptr) as usize) + extra
|
||||
}
|
||||
start
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -393,8 +393,8 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_segment_postings() -> crate::Result<()> {
|
||||
let mut block_segments = build_block_postings(&(0..100_000).collect::<Vec<u32>>())?;
|
||||
fn test_block_segment_postings() {
|
||||
let mut block_segments = build_block_postings(&(0..100_000).collect::<Vec<u32>>());
|
||||
let mut offset: u32 = 0u32;
|
||||
// checking that the `doc_freq` is correct
|
||||
assert_eq!(block_segments.doc_freq(), 100_000);
|
||||
@@ -409,17 +409,16 @@ mod tests {
|
||||
offset += block.len() as u32;
|
||||
block_segments.advance();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_skip_right_at_new_block() -> crate::Result<()> {
|
||||
fn test_skip_right_at_new_block() {
|
||||
let mut doc_ids = (0..128).collect::<Vec<u32>>();
|
||||
// 128 is missing
|
||||
doc_ids.push(129);
|
||||
doc_ids.push(130);
|
||||
{
|
||||
let block_segments = build_block_postings(&doc_ids)?;
|
||||
let block_segments = build_block_postings(&doc_ids);
|
||||
let mut docset = SegmentPostings::from_block_postings(block_segments, None);
|
||||
assert_eq!(docset.seek(128), 129);
|
||||
assert_eq!(docset.doc(), 129);
|
||||
@@ -428,7 +427,7 @@ mod tests {
|
||||
assert_eq!(docset.advance(), TERMINATED);
|
||||
}
|
||||
{
|
||||
let block_segments = build_block_postings(&doc_ids).unwrap();
|
||||
let block_segments = build_block_postings(&doc_ids);
|
||||
let mut docset = SegmentPostings::from_block_postings(block_segments, None);
|
||||
assert_eq!(docset.seek(129), 129);
|
||||
assert_eq!(docset.doc(), 129);
|
||||
@@ -437,47 +436,46 @@ mod tests {
|
||||
assert_eq!(docset.advance(), TERMINATED);
|
||||
}
|
||||
{
|
||||
let block_segments = build_block_postings(&doc_ids)?;
|
||||
let block_segments = build_block_postings(&doc_ids);
|
||||
let mut docset = SegmentPostings::from_block_postings(block_segments, None);
|
||||
assert_eq!(docset.doc(), 0);
|
||||
assert_eq!(docset.seek(131), TERMINATED);
|
||||
assert_eq!(docset.doc(), TERMINATED);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn build_block_postings(docs: &[DocId]) -> crate::Result<BlockSegmentPostings> {
|
||||
fn build_block_postings(docs: &[DocId]) -> BlockSegmentPostings {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let int_field = schema_builder.add_u64_field("id", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let mut last_doc = 0u32;
|
||||
for &doc in docs {
|
||||
for _ in last_doc..doc {
|
||||
index_writer.add_document(doc!(int_field=>1u64))?;
|
||||
index_writer.add_document(doc!(int_field=>1u64));
|
||||
}
|
||||
index_writer.add_document(doc!(int_field=>0u64))?;
|
||||
index_writer.add_document(doc!(int_field=>0u64));
|
||||
last_doc = doc + 1;
|
||||
}
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
index_writer.commit().unwrap();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let inverted_index = segment_reader.inverted_index(int_field).unwrap();
|
||||
let term = Term::from_field_u64(int_field, 0u64);
|
||||
let term_info = inverted_index.get_term_info(&term)?.unwrap();
|
||||
let block_postings = inverted_index
|
||||
.read_block_postings_from_terminfo(&term_info, IndexRecordOption::Basic)?;
|
||||
Ok(block_postings)
|
||||
let term_info = inverted_index.get_term_info(&term).unwrap().unwrap();
|
||||
inverted_index
|
||||
.read_block_postings_from_terminfo(&term_info, IndexRecordOption::Basic)
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_block_segment_postings_seek() -> crate::Result<()> {
|
||||
fn test_block_segment_postings_seek() {
|
||||
let mut docs = vec![0];
|
||||
for i in 0..1300 {
|
||||
docs.push((i * i / 100) + i);
|
||||
}
|
||||
let mut block_postings = build_block_postings(&docs[..])?;
|
||||
let mut block_postings = build_block_postings(&docs[..]);
|
||||
for i in &[0, 424, 10000] {
|
||||
block_postings.seek(*i);
|
||||
let docs = block_postings.docs();
|
||||
@@ -486,7 +484,6 @@ mod tests {
|
||||
}
|
||||
block_postings.seek(100_000);
|
||||
assert_eq!(block_postings.doc(COMPRESSION_BLOCK_SIZE - 1), TERMINATED);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -500,7 +497,7 @@ mod tests {
|
||||
// the other containing odd numbers.
|
||||
for i in 0..6 {
|
||||
let doc = doc!(int_field=> (i % 2) as u64);
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
|
||||
@@ -87,12 +87,12 @@ pub mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(title => r#"abc abc abc"#))?;
|
||||
index_writer.add_document(doc!(title => r#"abc be be be be abc"#))?;
|
||||
index_writer.add_document(doc!(title => r#"abc abc abc"#));
|
||||
index_writer.add_document(doc!(title => r#"abc be be be be abc"#));
|
||||
for _ in 0..1_000 {
|
||||
index_writer.add_document(doc!(title => r#"abc abc abc"#))?;
|
||||
index_writer.add_document(doc!(title => r#"abc abc abc"#));
|
||||
}
|
||||
index_writer.add_document(doc!(title => r#"abc be be be be abc"#))?;
|
||||
index_writer.add_document(doc!(title => r#"abc be be be be abc"#));
|
||||
index_writer.commit()?;
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
@@ -153,68 +153,50 @@ pub mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_index_max_length_token() -> crate::Result<()> {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_options = TextOptions::default().set_indexing_options(
|
||||
TextFieldIndexing::default()
|
||||
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
||||
.set_tokenizer("simple_no_truncation"),
|
||||
);
|
||||
let text_field = schema_builder.add_text_field("text", text_options);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
index
|
||||
.tokenizers()
|
||||
.register("simple_no_truncation", SimpleTokenizer);
|
||||
let reader = index.reader()?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
|
||||
let ok_token_text: String = "A".repeat(MAX_TOKEN_LEN);
|
||||
index_writer.add_document(doc!(text_field=>ok_token_text.clone()))?;
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
let searcher = reader.searcher();
|
||||
let segment_reader = searcher.segment_reader(0u32);
|
||||
let inverted_index = segment_reader.inverted_index(text_field)?;
|
||||
assert_eq!(inverted_index.terms().num_terms(), 1);
|
||||
let mut bytes = vec![];
|
||||
assert!(inverted_index.terms().ord_to_term(0, &mut bytes)?);
|
||||
assert_eq!(&bytes[..], ok_token_text.as_bytes());
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_drop_token_that_are_too_long() -> crate::Result<()> {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_options = TextOptions::default().set_indexing_options(
|
||||
TextFieldIndexing::default()
|
||||
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
||||
.set_tokenizer("simple_no_truncation"),
|
||||
);
|
||||
let text_field = schema_builder.add_text_field("text", text_options);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
index
|
||||
.tokenizers()
|
||||
.register("simple_no_truncation", SimpleTokenizer);
|
||||
let reader = index.reader()?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
|
||||
let ok_token_text: String = "A".repeat(MAX_TOKEN_LEN);
|
||||
let mut exceeding_token_text: String = "A".repeat(MAX_TOKEN_LEN + 1);
|
||||
exceeding_token_text.push_str(" hello");
|
||||
index_writer.add_document(doc!(text_field=>exceeding_token_text))?;
|
||||
index_writer.commit()?;
|
||||
reader.reload()?;
|
||||
let searcher = reader.searcher();
|
||||
let segment_reader = searcher.segment_reader(0u32);
|
||||
let inverted_index = segment_reader.inverted_index(text_field)?;
|
||||
assert_eq!(inverted_index.terms().num_terms(), 1);
|
||||
let mut bytes = vec![];
|
||||
assert!(inverted_index.terms().ord_to_term(0, &mut bytes)?);
|
||||
assert_eq!(&bytes, b"hello");
|
||||
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_options = TextOptions::default().set_indexing_options(
|
||||
TextFieldIndexing::default()
|
||||
.set_index_option(IndexRecordOption::WithFreqsAndPositions)
|
||||
.set_tokenizer("simple_no_truncation"),
|
||||
);
|
||||
let text_field = schema_builder.add_text_field("text", text_options);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
index
|
||||
.tokenizers()
|
||||
.register("simple_no_truncation", SimpleTokenizer);
|
||||
let reader = index.reader().unwrap();
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
{
|
||||
index_writer.add_document(doc!(text_field=>exceeding_token_text));
|
||||
index_writer.commit().unwrap();
|
||||
reader.reload().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let segment_reader = searcher.segment_reader(0u32);
|
||||
let inverted_index = segment_reader.inverted_index(text_field)?;
|
||||
assert_eq!(inverted_index.terms().num_terms(), 1);
|
||||
let mut bytes = vec![];
|
||||
assert!(inverted_index.terms().ord_to_term(0, &mut bytes)?);
|
||||
assert_eq!(&bytes, b"hello");
|
||||
}
|
||||
{
|
||||
index_writer.add_document(doc!(text_field=>ok_token_text.clone()));
|
||||
index_writer.commit().unwrap();
|
||||
reader.reload().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let segment_reader = searcher.segment_reader(1u32);
|
||||
let inverted_index = segment_reader.inverted_index(text_field)?;
|
||||
assert_eq!(inverted_index.terms().num_terms(), 1);
|
||||
let mut bytes = vec![];
|
||||
assert!(inverted_index.terms().ord_to_term(0, &mut bytes)?);
|
||||
assert_eq!(&bytes[..], ok_token_text.as_bytes());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -333,13 +315,13 @@ pub mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field => "g b b d c g c"))?;
|
||||
index_writer.add_document(doc!(text_field => "g a b b a d c g c"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field => "g b b d c g c"));
|
||||
index_writer.add_document(doc!(text_field => "g a b b a d c g c"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let term_a = Term::from_field_text(text_field, "a");
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let mut postings = segment_reader
|
||||
.inverted_index(text_field)?
|
||||
@@ -368,7 +350,7 @@ pub mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
for i in 0u64..num_docs as u64 {
|
||||
let doc = doc!(value_field => 2u64, value_field => i % 2u64);
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ fn posting_from_field_entry(field_entry: &FieldEntry) -> Box<dyn PostingsWriter>
|
||||
SpecializedPostingsWriter::<TfAndPositionRecorder>::new_boxed()
|
||||
}
|
||||
})
|
||||
.unwrap_or_else(SpecializedPostingsWriter::<NothingRecorder>::new_boxed),
|
||||
.unwrap_or_else(|| SpecializedPostingsWriter::<NothingRecorder>::new_boxed()),
|
||||
FieldType::U64(_)
|
||||
| FieldType::I64(_)
|
||||
| FieldType::F64(_)
|
||||
|
||||
@@ -13,7 +13,6 @@ use crate::termdict::{TermDictionaryBuilder, TermOrdinal};
|
||||
use crate::{DocId, Score};
|
||||
use common::CountingWriter;
|
||||
use common::{BinarySerializable, VInt};
|
||||
use fail::fail_point;
|
||||
use std::cmp::Ordering;
|
||||
use std::io::{self, Write};
|
||||
|
||||
@@ -213,9 +212,6 @@ impl<'a> FieldSerializer<'a> {
|
||||
/// If the current block is incomplete, it need to be encoded
|
||||
/// using `VInt` encoding.
|
||||
pub fn close_term(&mut self) -> io::Result<()> {
|
||||
fail_point!("FieldSerializer::close_term", |msg: Option<String>| {
|
||||
Err(io::Error::new(io::ErrorKind::Other, format!("{:?}", msg)))
|
||||
});
|
||||
if self.term_open {
|
||||
self.postings_serializer
|
||||
.close_term(self.current_term_info.doc_freq)?;
|
||||
|
||||
@@ -78,29 +78,29 @@ mod tests {
|
||||
use crate::schema::{Schema, TEXT};
|
||||
use crate::Index;
|
||||
|
||||
fn create_test_index() -> crate::Result<Index> {
|
||||
fn create_test_index() -> Index {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(field=>"aaa"))?;
|
||||
index_writer.add_document(doc!(field=>"bbb"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(field=>"ccc"))?;
|
||||
index_writer.commit()?;
|
||||
Ok(index)
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(field=>"aaa"));
|
||||
index_writer.add_document(doc!(field=>"bbb"));
|
||||
index_writer.commit().unwrap();
|
||||
index_writer.add_document(doc!(field=>"ccc"));
|
||||
index_writer.commit().unwrap();
|
||||
index
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_query() -> crate::Result<()> {
|
||||
let index = create_test_index()?;
|
||||
let reader = index.reader()?;
|
||||
fn test_all_query() {
|
||||
let index = create_test_index();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let weight = AllQuery.weight(&searcher, false)?;
|
||||
let weight = AllQuery.weight(&searcher, false).unwrap();
|
||||
{
|
||||
let reader = searcher.segment_reader(0);
|
||||
let mut scorer = weight.scorer(reader, 1.0)?;
|
||||
let mut scorer = weight.scorer(reader, 1.0).unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.advance(), 1u32);
|
||||
assert_eq!(scorer.doc(), 1u32);
|
||||
@@ -108,30 +108,28 @@ mod tests {
|
||||
}
|
||||
{
|
||||
let reader = searcher.segment_reader(1);
|
||||
let mut scorer = weight.scorer(reader, 1.0)?;
|
||||
let mut scorer = weight.scorer(reader, 1.0).unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.advance(), TERMINATED);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_all_query_with_boost() -> crate::Result<()> {
|
||||
let index = create_test_index()?;
|
||||
let reader = index.reader()?;
|
||||
fn test_all_query_with_boost() {
|
||||
let index = create_test_index();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let weight = AllQuery.weight(&searcher, false)?;
|
||||
let weight = AllQuery.weight(&searcher, false).unwrap();
|
||||
let reader = searcher.segment_reader(0);
|
||||
{
|
||||
let mut scorer = weight.scorer(reader, 2.0)?;
|
||||
let mut scorer = weight.scorer(reader, 2.0).unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.score(), 2.0);
|
||||
}
|
||||
{
|
||||
let mut scorer = weight.scorer(reader, 1.5)?;
|
||||
let mut scorer = weight.scorer(reader, 1.5).unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.score(), 1.5);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,16 +92,16 @@ mod tests {
|
||||
use crate::Index;
|
||||
use tantivy_fst::Automaton;
|
||||
|
||||
fn create_index() -> crate::Result<Index> {
|
||||
fn create_index() -> Index {
|
||||
let mut schema = Schema::builder();
|
||||
let title = schema.add_text_field("title", STRING);
|
||||
let index = Index::create_in_ram(schema.build());
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(title=>"abc"))?;
|
||||
index_writer.add_document(doc!(title=>"bcd"))?;
|
||||
index_writer.add_document(doc!(title=>"abcd"))?;
|
||||
index_writer.commit()?;
|
||||
Ok(index)
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(title=>"abc"));
|
||||
index_writer.add_document(doc!(title=>"bcd"));
|
||||
index_writer.add_document(doc!(title=>"abcd"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
@@ -140,32 +140,34 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_automaton_weight() -> crate::Result<()> {
|
||||
let index = create_index()?;
|
||||
fn test_automaton_weight() {
|
||||
let index = create_index();
|
||||
let field = index.schema().get_field("title").unwrap();
|
||||
let automaton_weight = AutomatonWeight::new(field, PrefixedByA);
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let mut scorer = automaton_weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let mut scorer = automaton_weight
|
||||
.scorer(searcher.segment_reader(0u32), 1.0)
|
||||
.unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.score(), 1.0);
|
||||
assert_eq!(scorer.advance(), 2u32);
|
||||
assert_eq!(scorer.doc(), 2u32);
|
||||
assert_eq!(scorer.score(), 1.0);
|
||||
assert_eq!(scorer.advance(), TERMINATED);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_automaton_weight_boost() -> crate::Result<()> {
|
||||
let index = create_index()?;
|
||||
fn test_automaton_weight_boost() {
|
||||
let index = create_index();
|
||||
let field = index.schema().get_field("title").unwrap();
|
||||
let automaton_weight = AutomatonWeight::new(field, PrefixedByA);
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let mut scorer = automaton_weight.scorer(searcher.segment_reader(0u32), 1.32)?;
|
||||
let mut scorer = automaton_weight
|
||||
.scorer(searcher.segment_reader(0u32), 1.32)
|
||||
.unwrap();
|
||||
assert_eq!(scorer.doc(), 0u32);
|
||||
assert_eq!(scorer.score(), 1.32);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,39 +42,27 @@ fn find_pivot_doc(
|
||||
Some((before_pivot_len, pivot_len, pivot_doc))
|
||||
}
|
||||
|
||||
/// Advance the scorer with best score among the scorers[..pivot_len] to
|
||||
/// the next doc candidate defined by the min of `last_doc_in_block + 1` for
|
||||
/// scorer in scorers[..pivot_len] and `scorer.doc()` for scorer in scorers[pivot_len..].
|
||||
/// Note: before and after calling this method, scorers need to be sorted by their `.doc()`.
|
||||
// Before and after calling this method, scorers need to be sorted by their `.doc()`.
|
||||
fn block_max_was_too_low_advance_one_scorer(
|
||||
scorers: &mut Vec<TermScorerWithMaxScore>,
|
||||
pivot_len: usize,
|
||||
) {
|
||||
debug_assert!(is_sorted(scorers.iter().map(|scorer| scorer.doc())));
|
||||
let mut scorer_to_seek = pivot_len - 1;
|
||||
let mut global_max_score = scorers[scorer_to_seek].max_score;
|
||||
let mut doc_to_seek_after = scorers[scorer_to_seek].last_doc_in_block();
|
||||
let mut doc_to_seek_after = scorers[scorer_to_seek].doc();
|
||||
for scorer_ord in (0..pivot_len - 1).rev() {
|
||||
let scorer = &scorers[scorer_ord];
|
||||
if scorer.last_doc_in_block() <= doc_to_seek_after {
|
||||
doc_to_seek_after = scorer.last_doc_in_block();
|
||||
}
|
||||
if scorers[scorer_ord].max_score > global_max_score {
|
||||
global_max_score = scorers[scorer_ord].max_score;
|
||||
scorer_to_seek = scorer_ord;
|
||||
}
|
||||
}
|
||||
// Add +1 to go to the next block unless we are already at the end.
|
||||
if doc_to_seek_after != TERMINATED {
|
||||
doc_to_seek_after += 1;
|
||||
}
|
||||
for scorer in &scorers[pivot_len..] {
|
||||
if scorer.doc() <= doc_to_seek_after {
|
||||
doc_to_seek_after = scorer.doc();
|
||||
}
|
||||
}
|
||||
scorers[scorer_to_seek].seek(doc_to_seek_after);
|
||||
|
||||
scorers[scorer_to_seek].seek(doc_to_seek_after + 1);
|
||||
restore_ordering(scorers, scorer_to_seek);
|
||||
debug_assert!(is_sorted(scorers.iter().map(|scorer| scorer.doc())));
|
||||
}
|
||||
@@ -142,9 +130,6 @@ fn advance_all_scorers_on_pivot(term_scorers: &mut Vec<TermScorerWithMaxScore>,
|
||||
term_scorers.sort_by_key(|scorer| scorer.doc());
|
||||
}
|
||||
|
||||
/// Implements the WAND (Weak AND) algorithm for dynamic pruning
|
||||
/// described in the paper "Faster Top-k Document Retrieval Using Block-Max Indexes".
|
||||
/// Link: http://engineering.nyu.edu/~suel/papers/bmw.pdf
|
||||
pub fn block_wand(
|
||||
mut scorers: Vec<TermScorer>,
|
||||
mut threshold: Score,
|
||||
@@ -202,7 +187,6 @@ pub fn block_wand(
|
||||
.iter_mut()
|
||||
.map(|scorer| scorer.score())
|
||||
.sum();
|
||||
|
||||
if score > threshold {
|
||||
threshold = callback(pivot_doc, score);
|
||||
}
|
||||
@@ -211,56 +195,6 @@ pub fn block_wand(
|
||||
}
|
||||
}
|
||||
|
||||
/// Specialized version of [`block_wand`] for a single scorer.
|
||||
/// In this case, the algorithm is simple and readable and faster (~ x3)
|
||||
/// than the generic algorithm.
|
||||
/// The algorithm behaves as follows:
|
||||
/// - While we don't hit the end of the docset:
|
||||
/// - While the block max score is under the `threshold`, go to the
|
||||
/// next block.
|
||||
/// - On a block, advance until the end and execute `callback``
|
||||
/// when the doc score is greater or equal to the `threshold`.
|
||||
pub fn block_wand_single_scorer(
|
||||
mut scorer: TermScorer,
|
||||
mut threshold: Score,
|
||||
callback: &mut dyn FnMut(u32, Score) -> Score,
|
||||
) {
|
||||
let mut doc = scorer.doc();
|
||||
loop {
|
||||
// We position the scorer on a block that can reach
|
||||
// the threshold.
|
||||
while scorer.block_max_score() < threshold {
|
||||
let last_doc_in_block = scorer.last_doc_in_block();
|
||||
if last_doc_in_block == TERMINATED {
|
||||
return;
|
||||
}
|
||||
doc = last_doc_in_block + 1;
|
||||
scorer.shallow_seek(doc);
|
||||
}
|
||||
// Seek will effectively load that block.
|
||||
doc = scorer.seek(doc);
|
||||
if doc == TERMINATED {
|
||||
break;
|
||||
}
|
||||
loop {
|
||||
let score = scorer.score();
|
||||
if score > threshold {
|
||||
threshold = callback(doc, score);
|
||||
}
|
||||
debug_assert!(doc <= scorer.last_doc_in_block());
|
||||
if doc == scorer.last_doc_in_block() {
|
||||
break;
|
||||
}
|
||||
doc = scorer.advance();
|
||||
if doc == TERMINATED {
|
||||
return;
|
||||
}
|
||||
}
|
||||
doc += 1;
|
||||
scorer.shallow_seek(doc);
|
||||
}
|
||||
}
|
||||
|
||||
struct TermScorerWithMaxScore<'a> {
|
||||
scorer: &'a mut TermScorer,
|
||||
max_score: Score,
|
||||
@@ -338,14 +272,13 @@ mod tests {
|
||||
}
|
||||
|
||||
fn compute_checkpoints_for_each_pruning(
|
||||
mut term_scorers: Vec<TermScorer>,
|
||||
term_scorers: Vec<TermScorer>,
|
||||
n: usize,
|
||||
) -> Vec<(DocId, Score)> {
|
||||
let mut heap: BinaryHeap<Float> = BinaryHeap::with_capacity(n);
|
||||
let mut checkpoints: Vec<(DocId, Score)> = Vec::new();
|
||||
let mut limit: Score = 0.0;
|
||||
|
||||
let callback = &mut |doc, score| {
|
||||
super::block_wand(term_scorers, Score::MIN, &mut |doc, score| {
|
||||
heap.push(Float(score));
|
||||
if heap.len() > n {
|
||||
heap.pop().unwrap();
|
||||
@@ -357,14 +290,7 @@ mod tests {
|
||||
checkpoints.push((doc, score));
|
||||
}
|
||||
limit
|
||||
};
|
||||
|
||||
if term_scorers.len() == 1 {
|
||||
let scorer = term_scorers.pop().unwrap();
|
||||
super::block_wand_single_scorer(scorer, Score::MIN, callback);
|
||||
} else {
|
||||
super::block_wand(term_scorers, Score::MIN, callback);
|
||||
}
|
||||
});
|
||||
checkpoints
|
||||
}
|
||||
|
||||
@@ -498,14 +424,6 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(500))]
|
||||
#[test]
|
||||
fn test_block_wand_single_term_scorer((posting_lists, fieldnorms) in gen_term_scorers(1)) {
|
||||
test_block_wand_aux(&posting_lists[..], &fieldnorms[..]);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fn_reproduce_proptest() {
|
||||
let postings_lists = &[
|
||||
|
||||
@@ -41,22 +41,22 @@ use std::collections::BTreeMap;
|
||||
/// let mut index_writer = index.writer(3_000_000)?;
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Name of the Wind",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of Muadib",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "A Dairy Cow",
|
||||
/// body => "hidden",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "A Dairy Cow",
|
||||
/// body => "found",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of a Young Girl",
|
||||
/// ))?;
|
||||
/// index_writer.commit()?;
|
||||
/// ));
|
||||
/// index_writer.commit().unwrap();
|
||||
/// }
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
@@ -217,11 +217,11 @@ mod tests {
|
||||
let text = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut writer = index.writer_for_tests()?;
|
||||
writer.add_document(doc!(text=>"b c"))?;
|
||||
writer.add_document(doc!(text=>"a c"))?;
|
||||
writer.add_document(doc!(text=>"a b"))?;
|
||||
writer.add_document(doc!(text=>"a d"))?;
|
||||
let mut writer = index.writer_for_tests().unwrap();
|
||||
writer.add_document(doc!(text=>"b c"));
|
||||
writer.add_document(doc!(text=>"a c"));
|
||||
writer.add_document(doc!(text=>"a b"));
|
||||
writer.add_document(doc!(text=>"a d"));
|
||||
writer.commit()?;
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@ mod boolean_query;
|
||||
mod boolean_weight;
|
||||
|
||||
pub(crate) use self::block_wand::block_wand;
|
||||
pub(crate) use self::block_wand::block_wand_single_scorer;
|
||||
pub use self::boolean_query::BooleanQuery;
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -26,75 +25,72 @@ mod tests {
|
||||
use crate::Index;
|
||||
use crate::{DocAddress, DocId, Score};
|
||||
|
||||
fn aux_test_helper() -> crate::Result<(Index, Field)> {
|
||||
fn aux_test_helper() -> (Index, Field) {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field => "a b c"))?;
|
||||
index_writer.add_document(doc!(text_field => "a c"))?;
|
||||
index_writer.add_document(doc!(text_field => "b c"))?;
|
||||
index_writer.add_document(doc!(text_field => "a b c d"))?;
|
||||
index_writer.add_document(doc!(text_field => "d"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field => "a b c"));
|
||||
index_writer.add_document(doc!(text_field => "a c"));
|
||||
index_writer.add_document(doc!(text_field => "b c"));
|
||||
index_writer.add_document(doc!(text_field => "a b c d"));
|
||||
index_writer.add_document(doc!(text_field => "d"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
Ok((index, text_field))
|
||||
(index, text_field)
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_non_all_term_disjunction() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_non_all_term_disjunction() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
let query = query_parser.parse_query("(+a +b) d")?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
assert_eq!(query.count(&searcher)?, 3);
|
||||
Ok(())
|
||||
let query = query_parser.parse_query("(+a +b) d").unwrap();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
assert_eq!(query.count(&searcher).unwrap(), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_single_must_clause() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_single_must_clause() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
let query = query_parser.parse_query("+a")?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let weight = query.weight(&searcher, true)?;
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let query = query_parser.parse_query("+a").unwrap();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let weight = query.weight(&searcher, true).unwrap();
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0).unwrap();
|
||||
assert!(scorer.is::<TermScorer>());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_termonly_intersection() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_termonly_intersection() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
{
|
||||
let query = query_parser.parse_query("+a +b +c")?;
|
||||
let weight = query.weight(&searcher, true)?;
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let query = query_parser.parse_query("+a +b +c").unwrap();
|
||||
let weight = query.weight(&searcher, true).unwrap();
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0).unwrap();
|
||||
assert!(scorer.is::<Intersection<TermScorer>>());
|
||||
}
|
||||
{
|
||||
let query = query_parser.parse_query("+a +(b c)")?;
|
||||
let weight = query.weight(&searcher, true)?;
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let query = query_parser.parse_query("+a +(b c)").unwrap();
|
||||
let weight = query.weight(&searcher, true).unwrap();
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0).unwrap();
|
||||
assert!(scorer.is::<Intersection<Box<dyn Scorer>>>());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_reqopt() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_reqopt() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
{
|
||||
let query = query_parser.parse_query("+a b")?;
|
||||
let weight = query.weight(&searcher, true)?;
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let query = query_parser.parse_query("+a b").unwrap();
|
||||
let weight = query.weight(&searcher, true).unwrap();
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0).unwrap();
|
||||
assert!(scorer.is::<RequiredOptionalScorer<
|
||||
Box<dyn Scorer>,
|
||||
Box<dyn Scorer>,
|
||||
@@ -102,17 +98,16 @@ mod tests {
|
||||
>>());
|
||||
}
|
||||
{
|
||||
let query = query_parser.parse_query("+a b")?;
|
||||
let weight = query.weight(&searcher, false)?;
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let query = query_parser.parse_query("+a b").unwrap();
|
||||
let weight = query.weight(&searcher, false).unwrap();
|
||||
let scorer = weight.scorer(searcher.segment_reader(0u32), 1.0).unwrap();
|
||||
assert!(scorer.is::<TermScorer>());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_query() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_query() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
|
||||
let make_term_query = |text: &str| {
|
||||
let term_query = TermQuery::new(
|
||||
@@ -123,7 +118,7 @@ mod tests {
|
||||
query
|
||||
};
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
|
||||
let matching_docs = |boolean_query: &dyn Query| {
|
||||
reader
|
||||
@@ -170,12 +165,11 @@ mod tests {
|
||||
let boolean_query = BooleanQuery::new(vec![(Occur::MustNot, make_term_query("d"))]);
|
||||
assert_eq!(matching_docs(&boolean_query), Vec::<u32>::new());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_query_two_excluded() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_boolean_query_two_excluded() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
|
||||
let make_term_query = |text: &str| {
|
||||
let term_query = TermQuery::new(
|
||||
@@ -186,7 +180,7 @@ mod tests {
|
||||
query
|
||||
};
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
|
||||
let matching_topdocs = |query: &dyn Query| {
|
||||
reader
|
||||
@@ -219,21 +213,20 @@ mod tests {
|
||||
assert_eq!(top_doc, DocAddress::new(0, 4));
|
||||
assert_eq!(top_score, score_doc_4);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_boolean_query_with_weight() -> crate::Result<()> {
|
||||
pub fn test_boolean_query_with_weight() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field => "a b c"))?;
|
||||
index_writer.add_document(doc!(text_field => "a c"))?;
|
||||
index_writer.add_document(doc!(text_field => "b c"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field => "a b c"));
|
||||
index_writer.add_document(doc!(text_field => "a c"));
|
||||
index_writer.add_document(doc!(text_field => "b c"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let term_a: Box<dyn Query> = Box::new(TermQuery::new(
|
||||
Term::from_field_text(text_field, "a"),
|
||||
@@ -249,21 +242,24 @@ mod tests {
|
||||
BooleanQuery::new(vec![(Occur::Should, term_a), (Occur::Should, term_b)]);
|
||||
let boolean_weight = boolean_query.weight(&searcher, true).unwrap();
|
||||
{
|
||||
let mut boolean_scorer = boolean_weight.scorer(searcher.segment_reader(0u32), 1.0)?;
|
||||
let mut boolean_scorer = boolean_weight
|
||||
.scorer(searcher.segment_reader(0u32), 1.0)
|
||||
.unwrap();
|
||||
assert_eq!(boolean_scorer.doc(), 0u32);
|
||||
assert_nearly_equals!(boolean_scorer.score(), 0.84163445);
|
||||
}
|
||||
{
|
||||
let mut boolean_scorer = boolean_weight.scorer(searcher.segment_reader(0u32), 2.0)?;
|
||||
let mut boolean_scorer = boolean_weight
|
||||
.scorer(searcher.segment_reader(0u32), 2.0)
|
||||
.unwrap();
|
||||
assert_eq!(boolean_scorer.doc(), 0u32);
|
||||
assert_nearly_equals!(boolean_scorer.score(), 1.6832689);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_intersection_score() -> crate::Result<()> {
|
||||
let (index, text_field) = aux_test_helper()?;
|
||||
pub fn test_intersection_score() {
|
||||
let (index, text_field) = aux_test_helper();
|
||||
|
||||
let make_term_query = |text: &str| {
|
||||
let term_query = TermQuery::new(
|
||||
@@ -273,7 +269,7 @@ mod tests {
|
||||
let query: Box<dyn Query> = Box::new(term_query);
|
||||
query
|
||||
};
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let score_docs = |boolean_query: &dyn Query| {
|
||||
let fruit = reader
|
||||
.searcher()
|
||||
@@ -291,7 +287,6 @@ mod tests {
|
||||
assert_nearly_equals!(scores[0], 0.977973);
|
||||
assert_nearly_equals!(scores[1], 0.84699446);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -301,8 +296,8 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_with_num_threads(1, 5_000_000)?;
|
||||
index_writer.add_document(doc!(text=>"a"))?;
|
||||
index_writer.add_document(doc!(text=>"b"))?;
|
||||
index_writer.add_document(doc!(text=>"a"));
|
||||
index_writer.add_document(doc!(text=>"b"));
|
||||
index_writer.commit()?;
|
||||
let searcher = index.reader()?.searcher();
|
||||
let term_a: Box<dyn Query> = Box::new(TermQuery::new(
|
||||
|
||||
@@ -141,20 +141,19 @@ mod tests {
|
||||
use crate::{DocAddress, Document, Index};
|
||||
|
||||
#[test]
|
||||
fn test_boost_query_explain() -> crate::Result<()> {
|
||||
fn test_boost_query_explain() {
|
||||
let schema = Schema::builder().build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(Document::new())?;
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(Document::new());
|
||||
assert!(index_writer.commit().is_ok());
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let query = BoostQuery::new(Box::new(AllQuery), 0.2);
|
||||
let explanation = query.explain(&searcher, DocAddress::new(0, 0u32)).unwrap();
|
||||
assert_eq!(
|
||||
explanation.to_pretty_json(),
|
||||
"{\n \"value\": 0.2,\n \"description\": \"Boost x0.2 of ...\",\n \"details\": [\n {\n \"value\": 1.0,\n \"description\": \"AllQuery\",\n \"context\": []\n }\n ],\n \"context\": []\n}"
|
||||
);
|
||||
Ok(())
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -67,17 +67,17 @@ static LEV_BUILDER: Lazy<HashMap<(u8, bool), LevenshteinAutomatonBuilder>> = Laz
|
||||
/// let mut index_writer = index.writer(3_000_000)?;
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Name of the Wind",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of Muadib",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "A Dairy Cow",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of a Young Girl",
|
||||
/// ))?;
|
||||
/// index_writer.commit()?;
|
||||
/// ));
|
||||
/// index_writer.commit().unwrap();
|
||||
/// }
|
||||
/// let reader = index.reader()?;
|
||||
/// let searcher = reader.searcher();
|
||||
@@ -129,7 +129,7 @@ impl FuzzyTermQuery {
|
||||
|
||||
fn specialized_weight(&self) -> crate::Result<AutomatonWeight<DfaWrapper>> {
|
||||
// LEV_BUILDER is a HashMap, whose `get` method returns an Option
|
||||
match LEV_BUILDER.get(&(self.distance, self.transposition_cost_one)) {
|
||||
match LEV_BUILDER.get(&(self.distance, false)) {
|
||||
// Unwrap the option and build the Ok(AutomatonWeight)
|
||||
Some(automaton_builder) => {
|
||||
let automaton = if self.prefix {
|
||||
@@ -164,7 +164,6 @@ impl Query for FuzzyTermQuery {
|
||||
mod test {
|
||||
use super::FuzzyTermQuery;
|
||||
use crate::assert_nearly_equals;
|
||||
use crate::collector::Count;
|
||||
use crate::collector::TopDocs;
|
||||
use crate::schema::Schema;
|
||||
use crate::schema::TEXT;
|
||||
@@ -172,29 +171,32 @@ mod test {
|
||||
use crate::Term;
|
||||
|
||||
#[test]
|
||||
pub fn test_fuzzy_term() -> crate::Result<()> {
|
||||
pub fn test_fuzzy_term() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let country_field = schema_builder.add_text_field("country", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
country_field => "japan",
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
country_field => "korea",
|
||||
))?;
|
||||
index_writer.commit()?;
|
||||
));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
|
||||
// passes because Levenshtein distance is 1 (substitute 'o' with 'a')
|
||||
{
|
||||
let term = Term::from_field_text(country_field, "japon");
|
||||
|
||||
let fuzzy_query = FuzzyTermQuery::new(term, 1, true);
|
||||
let top_docs = searcher.search(&fuzzy_query, &TopDocs::with_limit(2))?;
|
||||
let top_docs = searcher
|
||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
||||
.unwrap();
|
||||
assert_eq!(top_docs.len(), 1, "Expected only 1 document");
|
||||
let (score, _) = top_docs[0];
|
||||
assert_nearly_equals!(1.0, score);
|
||||
@@ -205,44 +207,23 @@ mod test {
|
||||
let term = Term::from_field_text(country_field, "jap");
|
||||
|
||||
let fuzzy_query = FuzzyTermQuery::new(term, 1, true);
|
||||
let top_docs = searcher.search(&fuzzy_query, &TopDocs::with_limit(2))?;
|
||||
let top_docs = searcher
|
||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
||||
.unwrap();
|
||||
assert_eq!(top_docs.len(), 0, "Expected no document");
|
||||
}
|
||||
|
||||
// passes because prefix Levenshtein distance is 0
|
||||
{
|
||||
let term = Term::from_field_text(country_field, "jap");
|
||||
|
||||
let fuzzy_query = FuzzyTermQuery::new_prefix(term, 1, true);
|
||||
let top_docs = searcher.search(&fuzzy_query, &TopDocs::with_limit(2))?;
|
||||
let top_docs = searcher
|
||||
.search(&fuzzy_query, &TopDocs::with_limit(2))
|
||||
.unwrap();
|
||||
assert_eq!(top_docs.len(), 1, "Expected only 1 document");
|
||||
let (score, _) = top_docs[0];
|
||||
assert_nearly_equals!(1.0, score);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_fuzzy_term_transposition_cost_one() -> crate::Result<()> {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let country_field = schema_builder.add_text_field("country", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(country_field => "japan"))?;
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
let term_jaapn = Term::from_field_text(country_field, "jaapn");
|
||||
{
|
||||
let fuzzy_query_transposition = FuzzyTermQuery::new(term_jaapn.clone(), 1, true);
|
||||
let count = searcher.search(&fuzzy_query_transposition, &Count)?;
|
||||
assert_eq!(count, 1);
|
||||
}
|
||||
{
|
||||
let fuzzy_query_transposition = FuzzyTermQuery::new(term_jaapn, 1, false);
|
||||
let count = searcher.search(&fuzzy_query_transposition, &Count)?;
|
||||
assert_eq!(count, 0);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
70
src/query/intersection_two.rs
Normal file
70
src/query/intersection_two.rs
Normal file
@@ -0,0 +1,70 @@
|
||||
use docset::DocSet;
|
||||
use query::Scorer;
|
||||
use DocId;
|
||||
use Score;
|
||||
use SkipResult;
|
||||
|
||||
|
||||
/// Creates a `DocSet` that iterate through the intersection of two `DocSet`s.
|
||||
pub struct IntersectionTwoTerms<TDocSet> {
|
||||
left: TDocSet,
|
||||
right: TDocSet
|
||||
}
|
||||
|
||||
impl<TDocSet: DocSet> IntersectionTwoTerms<TDocSet> {
|
||||
pub fn new(left: TDocSet, right: TDocSet) -> IntersectionTwoTerms<TDocSet> {
|
||||
IntersectionTwoTerms {
|
||||
left,
|
||||
right
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TDocSet: DocSet> DocSet for IntersectionTwoTerms<TDocSet> {
|
||||
|
||||
fn advance(&mut self) -> bool {
|
||||
let (left, right) = (&mut self.left, &mut self.right);
|
||||
if !left.advance() {
|
||||
return false;
|
||||
}
|
||||
let mut candidate = left.doc();
|
||||
loop {
|
||||
match right.skip_next(candidate) {
|
||||
SkipResult::Reached => {
|
||||
return true;
|
||||
}
|
||||
SkipResult::End => {
|
||||
return false;
|
||||
}
|
||||
SkipResult::OverStep => {
|
||||
candidate = right.doc();
|
||||
}
|
||||
}
|
||||
match left.skip_next(candidate) {
|
||||
SkipResult::Reached => {
|
||||
return true;
|
||||
}
|
||||
SkipResult::End => {
|
||||
return false;
|
||||
}
|
||||
SkipResult::OverStep => {
|
||||
candidate = left.doc();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn doc(&self) -> DocId {
|
||||
self.left.doc()
|
||||
}
|
||||
|
||||
fn size_hint(&self) -> u32 {
|
||||
self.left.size_hint().min(self.right.size_hint())
|
||||
}
|
||||
}
|
||||
|
||||
impl<TScorer: Scorer> Scorer for IntersectionTwoTerms<TScorer> {
|
||||
fn score(&mut self) -> Score {
|
||||
self.left.score() + self.right.score()
|
||||
}
|
||||
}
|
||||
@@ -61,11 +61,19 @@ impl Query for MoreLikeThisQuery {
|
||||
}
|
||||
|
||||
/// The builder for more-like-this query
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MoreLikeThisQueryBuilder {
|
||||
mlt: MoreLikeThis,
|
||||
}
|
||||
|
||||
impl Default for MoreLikeThisQueryBuilder {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
mlt: MoreLikeThis::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MoreLikeThisQueryBuilder {
|
||||
/// Sets the minimum document frequency.
|
||||
///
|
||||
@@ -176,20 +184,20 @@ mod tests {
|
||||
use crate::DocAddress;
|
||||
use crate::Index;
|
||||
|
||||
fn create_test_index() -> crate::Result<Index> {
|
||||
fn create_test_index() -> Index {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let title = schema_builder.add_text_field("title", TEXT);
|
||||
let body = schema_builder.add_text_field("body", TEXT | STORED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(title => "aaa", body => "the old man and the sea"))?;
|
||||
index_writer.add_document(doc!(title => "bbb", body => "an old man sailing on the sea"))?;
|
||||
index_writer.add_document(doc!(title => "ccc", body=> "send this message to alice"))?;
|
||||
index_writer.add_document(doc!(title => "ddd", body=> "a lady was riding and old bike"))?;
|
||||
index_writer.add_document(doc!(title => "eee", body=> "Yes, my lady."))?;
|
||||
index_writer.commit()?;
|
||||
Ok(index)
|
||||
index_writer.add_document(doc!(title => "aaa", body => "the old man and the sea"));
|
||||
index_writer.add_document(doc!(title => "bbb", body => "an old man sailing on the sea"));
|
||||
index_writer.add_document(doc!(title => "ccc", body=> "send this message to alice"));
|
||||
index_writer.add_document(doc!(title => "ddd", body=> "a lady was riding and old bike"));
|
||||
index_writer.add_document(doc!(title => "eee", body=> "Yes, my lady."));
|
||||
index_writer.commit().unwrap();
|
||||
index
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -235,9 +243,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_more_like_this_query() -> crate::Result<()> {
|
||||
let index = create_test_index()?;
|
||||
let reader = index.reader()?;
|
||||
fn test_more_like_this_query() {
|
||||
let index = create_test_index();
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
|
||||
// search base 1st doc with words [sea, and] skipping [old]
|
||||
@@ -250,7 +258,7 @@ mod tests {
|
||||
.with_boost_factor(1.0)
|
||||
.with_stop_words(vec!["old".to_string()])
|
||||
.with_document(DocAddress::new(0, 0));
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(5))?;
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(5)).unwrap();
|
||||
let mut doc_ids: Vec<_> = top_docs.iter().map(|item| item.1.doc_id).collect();
|
||||
doc_ids.sort_unstable();
|
||||
|
||||
@@ -266,12 +274,11 @@ mod tests {
|
||||
.with_max_word_length(5)
|
||||
.with_boost_factor(1.0)
|
||||
.with_document(DocAddress::new(0, 4));
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(5))?;
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(5)).unwrap();
|
||||
let mut doc_ids: Vec<_> = top_docs.iter().map(|item| item.1.doc_id).collect();
|
||||
doc_ids.sort_unstable();
|
||||
|
||||
assert_eq!(doc_ids.len(), 2);
|
||||
assert_eq!(doc_ids, vec![3, 4]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,34 +18,34 @@ pub mod tests {
|
||||
use crate::DocId;
|
||||
use crate::{DocAddress, TERMINATED};
|
||||
|
||||
pub fn create_index(texts: &[&'static str]) -> crate::Result<Index> {
|
||||
pub fn create_index(texts: &[&'static str]) -> Index {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
for &text in texts {
|
||||
let doc = doc!(text_field=>text);
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
Ok(index)
|
||||
index
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_query() -> crate::Result<()> {
|
||||
pub fn test_phrase_query() {
|
||||
let index = create_index(&[
|
||||
"b b b d c g c",
|
||||
"a b b d c g c",
|
||||
"a b a b c",
|
||||
"c a b a d ga a",
|
||||
"a b c",
|
||||
])?;
|
||||
]);
|
||||
let schema = index.schema();
|
||||
let text_field = schema.get_field("text").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let test_query = |texts: Vec<&str>| {
|
||||
let terms: Vec<Term> = texts
|
||||
.iter()
|
||||
@@ -54,7 +54,7 @@ pub mod tests {
|
||||
let phrase_query = PhraseQuery::new(terms);
|
||||
let test_fruits = searcher
|
||||
.search(&phrase_query, &TEST_COLLECTOR_WITH_SCORE)
|
||||
.unwrap();
|
||||
.expect("search should succeed");
|
||||
test_fruits
|
||||
.docs()
|
||||
.iter()
|
||||
@@ -66,12 +66,11 @@ pub mod tests {
|
||||
assert_eq!(test_query(vec!["b", "b"]), vec![0, 1]);
|
||||
assert!(test_query(vec!["g", "ewrwer"]).is_empty());
|
||||
assert!(test_query(vec!["g", "a"]).is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_query_simple() -> crate::Result<()> {
|
||||
let index = create_index(&["a b b d c g c", "a b a b c"])?;
|
||||
let index = create_index(&["a b b d c g c", "a b a b c"]);
|
||||
let text_field = index.schema().get_field("text").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
let terms: Vec<Term> = vec!["a", "b", "c"]
|
||||
@@ -87,17 +86,17 @@ pub mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_query_no_score() -> crate::Result<()> {
|
||||
pub fn test_phrase_query_no_score() {
|
||||
let index = create_index(&[
|
||||
"b b b d c g c",
|
||||
"a b b d c g c",
|
||||
"a b a b c",
|
||||
"c a b a d ga a",
|
||||
"a b c",
|
||||
])?;
|
||||
]);
|
||||
let schema = index.schema();
|
||||
let text_field = schema.get_field("text").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let test_query = |texts: Vec<&str>| {
|
||||
let terms: Vec<Term> = texts
|
||||
.iter()
|
||||
@@ -106,7 +105,7 @@ pub mod tests {
|
||||
let phrase_query = PhraseQuery::new(terms);
|
||||
let test_fruits = searcher
|
||||
.search(&phrase_query, &TEST_COLLECTOR_WITHOUT_SCORE)
|
||||
.unwrap();
|
||||
.expect("search should succeed");
|
||||
test_fruits
|
||||
.docs()
|
||||
.iter()
|
||||
@@ -118,11 +117,10 @@ pub mod tests {
|
||||
assert_eq!(test_query(vec!["b", "b"]), vec![0, 1]);
|
||||
assert!(test_query(vec!["g", "ewrwer"]).is_empty());
|
||||
assert!(test_query(vec!["g", "a"]).is_empty());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_query_no_positions() -> crate::Result<()> {
|
||||
pub fn test_phrase_query_no_positions() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
use crate::schema::IndexRecordOption;
|
||||
use crate::schema::TextFieldIndexing;
|
||||
@@ -137,34 +135,33 @@ pub mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"a b c"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let phrase_query = PhraseQuery::new(vec![
|
||||
Term::from_field_text(text_field, "a"),
|
||||
Term::from_field_text(text_field, "b"),
|
||||
]);
|
||||
|
||||
let search_error = searcher
|
||||
let search_result = searcher
|
||||
.search(&phrase_query, &TEST_COLLECTOR_WITH_SCORE)
|
||||
.err();
|
||||
.map(|_| ());
|
||||
assert!(matches!(
|
||||
search_error,
|
||||
Some(crate::TantivyError::SchemaError(msg))
|
||||
search_result,
|
||||
Err(crate::TantivyError::SchemaError(msg))
|
||||
if msg == "Applied phrase query on field \"text\", which does not have positions \
|
||||
indexed"
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_score() -> crate::Result<()> {
|
||||
let index = create_index(&["a b c", "a b c a b"])?;
|
||||
pub fn test_phrase_score() {
|
||||
let index = create_index(&["a b c", "a b c a b"]);
|
||||
let schema = index.schema();
|
||||
let text_field = schema.get_field("text").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let test_query = |texts: Vec<&str>| {
|
||||
let terms: Vec<Term> = texts
|
||||
.iter()
|
||||
@@ -180,24 +177,23 @@ pub mod tests {
|
||||
let scores = test_query(vec!["a", "b"]);
|
||||
assert_nearly_equals!(scores[0], 0.40618482);
|
||||
assert_nearly_equals!(scores[1], 0.46844664);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] // motivated by #234
|
||||
pub fn test_phrase_query_docfreq_order() -> crate::Result<()> {
|
||||
pub fn test_phrase_query_docfreq_order() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b a"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
index_writer.add_document(doc!(text_field=>"b a"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let test_query = |texts: Vec<&str>| {
|
||||
let terms: Vec<Term> = texts
|
||||
.iter()
|
||||
@@ -212,19 +208,18 @@ pub mod tests {
|
||||
};
|
||||
assert_eq!(test_query(vec!["a", "b"]), vec![DocAddress::new(0, 1)]);
|
||||
assert_eq!(test_query(vec!["b", "a"]), vec![DocAddress::new(0, 2)]);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test] // motivated by #234
|
||||
pub fn test_phrase_query_non_trivial_offsets() -> crate::Result<()> {
|
||||
pub fn test_phrase_query_non_trivial_offsets() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b c d e f g h"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"a b c d e f g h"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let test_query = |texts: Vec<(usize, &str)>| {
|
||||
@@ -250,6 +245,5 @@ pub mod tests {
|
||||
assert_eq!(test_query(vec![(4, "e"), (0, "a"), (2, "c")]), vec![0]);
|
||||
assert!(test_query(vec![(0, "a"), (2, "d")]).is_empty());
|
||||
assert_eq!(test_query(vec![(1, "a"), (3, "c")]), vec![0]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,18 +116,19 @@ mod tests {
|
||||
use crate::{DocSet, Term};
|
||||
|
||||
#[test]
|
||||
pub fn test_phrase_count() -> crate::Result<()> {
|
||||
let index = create_index(&["a c", "a a b d a b c", " a b"])?;
|
||||
pub fn test_phrase_count() {
|
||||
let index = create_index(&["a c", "a a b d a b c", " a b"]);
|
||||
let schema = index.schema();
|
||||
let text_field = schema.get_field("text").unwrap();
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let phrase_query = PhraseQuery::new(vec![
|
||||
Term::from_field_text(text_field, "a"),
|
||||
Term::from_field_text(text_field, "b"),
|
||||
]);
|
||||
let phrase_weight = phrase_query.phrase_weight(&searcher, true).unwrap();
|
||||
let mut phrase_scorer = phrase_weight
|
||||
.phrase_scorer(searcher.segment_reader(0u32), 1.0)?
|
||||
.phrase_scorer(searcher.segment_reader(0u32), 1.0)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(phrase_scorer.doc(), 1);
|
||||
assert_eq!(phrase_scorer.phrase_count(), 2);
|
||||
@@ -135,6 +136,5 @@ mod tests {
|
||||
assert_eq!(phrase_scorer.doc(), 2);
|
||||
assert_eq!(phrase_scorer.phrase_count(), 1);
|
||||
assert_eq!(phrase_scorer.advance(), TERMINATED);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ fn map_bound<TFrom, TTo, Transform: Fn(&TFrom) -> TTo>(
|
||||
/// for year in 1950u64..2017u64 {
|
||||
/// let num_docs_within_year = 10 + (year - 1950) * (year - 1950);
|
||||
/// 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()?;
|
||||
@@ -338,35 +338,34 @@ mod tests {
|
||||
use std::ops::Bound;
|
||||
|
||||
#[test]
|
||||
fn test_range_query_simple() -> crate::Result<()> {
|
||||
fn test_range_query_simple() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let year_field = schema_builder.add_u64_field("year", INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
for year in 1950u64..2017u64 {
|
||||
let num_docs_within_year = 10 + (year - 1950) * (year - 1950);
|
||||
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()?;
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().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)?;
|
||||
let count = searcher.search(&docs_in_the_sixties, &Count).unwrap();
|
||||
assert_eq!(count, 2285);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_query() -> crate::Result<()> {
|
||||
fn test_range_query() {
|
||||
let int_field: Field;
|
||||
let schema = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
@@ -376,7 +375,7 @@ mod tests {
|
||||
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_with_num_threads(2, 6_000_000)?;
|
||||
let mut index_writer = index.writer_with_num_threads(2, 6_000_000).unwrap();
|
||||
|
||||
for i in 1..100 {
|
||||
let mut doc = Document::new();
|
||||
@@ -385,10 +384,10 @@ mod tests {
|
||||
doc.add_i64(int_field, j as i64);
|
||||
}
|
||||
}
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
@@ -420,11 +419,10 @@ mod tests {
|
||||
)),
|
||||
91
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range_float() -> crate::Result<()> {
|
||||
fn test_range_float() {
|
||||
let float_field: Field;
|
||||
let schema = {
|
||||
let mut schema_builder = Schema::builder();
|
||||
@@ -443,12 +441,12 @@ mod tests {
|
||||
doc.add_f64(float_field, j as f64);
|
||||
}
|
||||
}
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let count_multiples =
|
||||
|range_query: RangeQuery| searcher.search(&range_query, &Count).unwrap();
|
||||
@@ -481,7 +479,6 @@ mod tests {
|
||||
)),
|
||||
91
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -497,7 +494,7 @@ mod tests {
|
||||
index_writer.add_document(doc!(
|
||||
title => "hemoglobin blood",
|
||||
year => 1990_i64
|
||||
))?;
|
||||
));
|
||||
index_writer.commit()?;
|
||||
let reader = index.reader()?;
|
||||
let searcher = reader.searcher();
|
||||
|
||||
@@ -28,17 +28,17 @@ use tantivy_fst::Regex;
|
||||
/// let mut index_writer = index.writer(3_000_000)?;
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Name of the Wind",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of Muadib",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "A Dairy Cow",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of a Young Girl",
|
||||
/// ))?;
|
||||
/// index_writer.commit()?;
|
||||
/// ));
|
||||
/// index_writer.commit().unwrap();
|
||||
/// }
|
||||
///
|
||||
/// let reader = index.reader()?;
|
||||
@@ -100,7 +100,7 @@ mod test {
|
||||
use std::sync::Arc;
|
||||
use tantivy_fst::Regex;
|
||||
|
||||
fn build_test_index() -> crate::Result<(IndexReader, Field)> {
|
||||
fn build_test_index() -> (IndexReader, Field) {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let country_field = schema_builder.add_text_field("country", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
@@ -109,15 +109,15 @@ mod test {
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
country_field => "japan",
|
||||
))?;
|
||||
));
|
||||
index_writer.add_document(doc!(
|
||||
country_field => "korea",
|
||||
))?;
|
||||
index_writer.commit()?;
|
||||
));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
|
||||
Ok((reader, country_field))
|
||||
(reader, country_field)
|
||||
}
|
||||
|
||||
fn verify_regex_query(
|
||||
@@ -141,32 +141,31 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_regex_query() -> crate::Result<()> {
|
||||
let (reader, field) = build_test_index()?;
|
||||
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();
|
||||
|
||||
let matching_one = RegexQuery::from_pattern("jap[ao]n", field)?;
|
||||
let matching_zero = RegexQuery::from_pattern("jap[A-Z]n", field)?;
|
||||
verify_regex_query(matching_one, matching_zero, reader);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_construct_from_regex() -> crate::Result<()> {
|
||||
let (reader, field) = build_test_index()?;
|
||||
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);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_construct_from_reused_regex() -> crate::Result<()> {
|
||||
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 (reader, field) = build_test_index();
|
||||
|
||||
let matching_one = RegexQuery::from_regex(r1.clone(), field);
|
||||
let matching_zero = RegexQuery::from_regex(r2.clone(), field);
|
||||
@@ -177,6 +176,5 @@ mod test {
|
||||
let matching_zero = RegexQuery::from_regex(r2, field);
|
||||
|
||||
verify_regex_query(matching_one, matching_zero, reader);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,29 +18,28 @@ mod tests {
|
||||
use crate::{Index, Term, TERMINATED};
|
||||
|
||||
#[test]
|
||||
pub fn test_term_query_no_freq() -> crate::Result<()> {
|
||||
pub fn test_term_query_no_freq() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", STRING);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
let doc = doc!(text_field => "a");
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc);
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let term_query = TermQuery::new(
|
||||
Term::from_field_text(text_field, "a"),
|
||||
IndexRecordOption::Basic,
|
||||
);
|
||||
let term_weight = term_query.weight(&searcher, true)?;
|
||||
let term_weight = term_query.weight(&searcher, true).unwrap();
|
||||
let segment_reader = searcher.segment_reader(0);
|
||||
let mut term_scorer = term_weight.scorer(segment_reader, 1.0)?;
|
||||
let mut term_scorer = term_weight.scorer(segment_reader, 1.0).unwrap();
|
||||
assert_eq!(term_scorer.doc(), 0);
|
||||
assert_nearly_equals!(term_scorer.score(), 0.28768212);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -54,7 +53,7 @@ mod tests {
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
for _ in 0..COMPRESSION_BLOCK_SIZE {
|
||||
let doc = doc!(text_field => "a");
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit()?;
|
||||
}
|
||||
@@ -79,7 +78,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_term_weight() -> crate::Result<()> {
|
||||
pub fn test_term_weight() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let left_field = schema_builder.add_text_field("left", TEXT);
|
||||
let right_field = schema_builder.add_text_field("right", TEXT);
|
||||
@@ -87,20 +86,22 @@ mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(
|
||||
left_field => "left1 left2 left2 left2f2 left2f2 left3 abcde abcde abcde abcde abcde abcde abcde abcde abcde abcewde abcde abcde",
|
||||
right_field => "right1 right2",
|
||||
large_field => "large0 large1 large2 large3 large4 large5 large6 large7 large8 large9 large10 large11 large12 large13 large14 large15 large16 large17 large18 large19 large20 large21 large22 large23 large24 large25 large26 large27 large28 large29 large30 large31 large32 large33 large34 large35 large36 large37 large38 large39 large40 large41 large42 large43 large44 large45 large46 large47 large48 large49 large50 large51 large52 large53 large54 large55 large56 large57 large58 large59 large60 large61 large62 large63 large64 large65 large66 large67 large68 large69 large70 large71 large72 large73 large74 large75 large76 large77 large78 large79 large80 large81 large82 large83 large84 large85 large86 large87 large88 large89 large90 large91 large92 large93 large94 large95 large96 large97 large98 large99 large100 large101 large102 large103 large104 large105 large106 large107 large108 large109 large110 large111 large112 large113 large114 large115 large116 large117 large118 large119 large120 large121 large122 large123 large124 large125 large126 large127 large128 large129 large130 large131 large132 large133 large134 large135 large136 large137 large138 large139 large140 large141 large142 large143 large144 large145 large146 large147 large148 large149 large150 large151 large152 large153 large154 large155 large156 large157 large158 large159 large160 large161 large162 large163 large164 large165 large166 large167 large168 large169 large170 large171 large172 large173 large174 large175 large176 large177 large178 large179 large180 large181 large182 large183 large184 large185 large186 large187 large188 large189 large190 large191 large192 large193 large194 large195 large196 large197 large198 large199 large200 large201 large202 large203 large204 large205 large206 large207 large208 large209 large210 large211 large212 large213 large214 large215 large216 large217 large218 large219 large220 large221 large222 large223 large224 large225 large226 large227 large228 large229 large230 large231 large232 large233 large234 large235 large236 large237 large238 large239 large240 large241 large242 large243 large244 large245 large246 large247 large248 large249 large250 large251 large252 large253 large254 large255 large256 large257 large258 large259 large260 large261 large262 large263 large264 large265 large266 large267 large268 large269 large270 large271 large272 large273 large274 large275 large276 large277 large278 large279 large280 large281 large282 large283 large284 large285 large286"
|
||||
))?;
|
||||
index_writer.add_document(doc!(left_field => "left4 left1"))?;
|
||||
index_writer.commit()?;
|
||||
));
|
||||
index_writer.add_document(doc!(left_field => "left4 left1"));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
{
|
||||
let term = Term::from_field_text(left_field, "left2");
|
||||
let term_query = TermQuery::new(term, IndexRecordOption::WithFreqs);
|
||||
let topdocs = searcher.search(&term_query, &TopDocs::with_limit(2))?;
|
||||
let topdocs = searcher
|
||||
.search(&term_query, &TopDocs::with_limit(2))
|
||||
.unwrap();
|
||||
assert_eq!(topdocs.len(), 1);
|
||||
let (score, _) = topdocs[0];
|
||||
assert_nearly_equals!(0.77802235, score);
|
||||
@@ -108,7 +109,9 @@ mod tests {
|
||||
{
|
||||
let term = Term::from_field_text(left_field, "left1");
|
||||
let term_query = TermQuery::new(term, IndexRecordOption::WithFreqs);
|
||||
let top_docs = searcher.search(&term_query, &TopDocs::with_limit(2))?;
|
||||
let top_docs = searcher
|
||||
.search(&term_query, &TopDocs::with_limit(2))
|
||||
.unwrap();
|
||||
assert_eq!(top_docs.len(), 2);
|
||||
let (score1, _) = top_docs[0];
|
||||
assert_nearly_equals!(0.27101856, score1);
|
||||
@@ -116,34 +119,32 @@ mod tests {
|
||||
assert_nearly_equals!(0.13736556, score2);
|
||||
}
|
||||
{
|
||||
let query_parser = QueryParser::for_index(&index, Vec::new());
|
||||
let query = query_parser.parse_query("left:left2 left:left1")?;
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(2))?;
|
||||
let query_parser = QueryParser::for_index(&index, vec![]);
|
||||
let query = query_parser.parse_query("left:left2 left:left1").unwrap();
|
||||
let top_docs = searcher.search(&query, &TopDocs::with_limit(2)).unwrap();
|
||||
assert_eq!(top_docs.len(), 2);
|
||||
let (score1, _) = top_docs[0];
|
||||
assert_nearly_equals!(0.9153879, score1);
|
||||
let (score2, _) = top_docs[1];
|
||||
assert_nearly_equals!(0.27101856, score2);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_term_query_count_when_there_are_deletes() -> crate::Result<()> {
|
||||
fn test_term_query_count_when_there_are_deletes() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a c"))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"a b"));
|
||||
index_writer.add_document(doc!(text_field=>"a c"));
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "b"));
|
||||
index_writer.commit()?;
|
||||
index_writer.commit().unwrap();
|
||||
let term_a = Term::from_field_text(text_field, "a");
|
||||
let term_query = TermQuery::new(term_a, IndexRecordOption::Basic);
|
||||
let reader = index.reader()?;
|
||||
assert_eq!(term_query.count(&*reader.searcher())?, 1);
|
||||
Ok(())
|
||||
let reader = index.reader().unwrap();
|
||||
assert_eq!(term_query.count(&*reader.searcher()).unwrap(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -152,9 +153,9 @@ mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.commit()?;
|
||||
let term_a = Term::from_field_text(text_field, "a");
|
||||
let term_query = TermQuery::new(term_a, IndexRecordOption::Basic);
|
||||
@@ -185,11 +186,11 @@ mod tests {
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"a"))?;
|
||||
index_writer.add_document(doc!(text_field=>"b"))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"a"));
|
||||
index_writer.add_document(doc!(text_field=>"b"));
|
||||
index_writer.commit()?;
|
||||
let term_a = Term::from_field_text(text_field, "a");
|
||||
let term_query = TermQuery::new(term_a, IndexRecordOption::Basic);
|
||||
|
||||
@@ -32,16 +32,16 @@ use std::fmt;
|
||||
/// let mut index_writer = index.writer(3_000_000)?;
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Name of the Wind",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of Muadib",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "A Dairy Cow",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.add_document(doc!(
|
||||
/// title => "The Diary of a Young Girl",
|
||||
/// ))?;
|
||||
/// ));
|
||||
/// index_writer.commit()?;
|
||||
/// }
|
||||
/// let reader = index.reader()?;
|
||||
|
||||
@@ -305,7 +305,7 @@ mod tests {
|
||||
let term_freq = rng.gen_range(1..10000);
|
||||
let words: Vec<&str> = std::iter::repeat("bbbb").take(term_freq).collect();
|
||||
let text = words.join(" ");
|
||||
writer.add_document(doc!(text_field=>text))?;
|
||||
writer.add_document(doc!(text_field=>text));
|
||||
}
|
||||
writer.commit()?;
|
||||
let term_query = TermQuery::new(
|
||||
|
||||
@@ -79,7 +79,7 @@ impl Weight for TermWeight {
|
||||
callback: &mut dyn FnMut(DocId, Score) -> Score,
|
||||
) -> crate::Result<()> {
|
||||
let scorer = self.specialized_scorer(reader, 1.0)?;
|
||||
crate::query::boolean_query::block_wand_single_scorer(scorer, threshold, callback);
|
||||
crate::query::boolean_query::block_wand(vec![scorer], threshold, callback);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,7 @@ impl IndexReaderBuilder {
|
||||
}
|
||||
Ok(IndexReader {
|
||||
inner: inner_reader_arc,
|
||||
_watch_handle_opt: watch_handle_opt,
|
||||
watch_handle_opt,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -167,7 +167,7 @@ impl InnerIndexReader {
|
||||
#[derive(Clone)]
|
||||
pub struct IndexReader {
|
||||
inner: Arc<InnerIndexReader>,
|
||||
_watch_handle_opt: Option<WatchHandle>,
|
||||
watch_handle_opt: Option<WatchHandle>,
|
||||
}
|
||||
|
||||
impl IndexReader {
|
||||
|
||||
@@ -3,7 +3,7 @@ use std::ops::BitOr;
|
||||
|
||||
use super::flags::{FastFlag, IndexedFlag, SchemaFlagList, StoredFlag};
|
||||
/// Define how an a bytes field should be handled by tantivy.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct BytesOptions {
|
||||
indexed: bool,
|
||||
fast: bool,
|
||||
@@ -56,6 +56,16 @@ impl BytesOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for BytesOptions {
|
||||
fn default() -> BytesOptions {
|
||||
BytesOptions {
|
||||
indexed: false,
|
||||
fast: false,
|
||||
stored: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Into<BytesOptions>> BitOr<T> for BytesOptions {
|
||||
type Output = BytesOptions;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ use serde::{Deserialize, Serialize};
|
||||
use std::ops::BitOr;
|
||||
|
||||
/// Define how a facet field should be handled by tantivy.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct FacetOptions {
|
||||
indexed: bool,
|
||||
stored: bool,
|
||||
@@ -39,6 +39,15 @@ impl FacetOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FacetOptions {
|
||||
fn default() -> FacetOptions {
|
||||
FacetOptions {
|
||||
indexed: false,
|
||||
stored: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for FacetOptions {
|
||||
fn from(_: ()) -> FacetOptions {
|
||||
FacetOptions::default()
|
||||
|
||||
@@ -4,7 +4,10 @@ use crate::schema::{is_valid_field_name, IntOptions};
|
||||
|
||||
use crate::schema::bytes_options::BytesOptions;
|
||||
use crate::schema::FieldType;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::{self, MapAccess, Visitor};
|
||||
use serde::ser::SerializeStruct;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::fmt;
|
||||
|
||||
/// A `FieldEntry` represents a field and its configuration.
|
||||
/// `Schema` are a collection of `FieldEntry`
|
||||
@@ -13,10 +16,9 @@ use serde::{Deserialize, Serialize};
|
||||
/// - a field name
|
||||
/// - a field type, itself wrapping up options describing
|
||||
/// how the field should be indexed.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub struct FieldEntry {
|
||||
name: String,
|
||||
#[serde(flatten)]
|
||||
field_type: FieldType,
|
||||
}
|
||||
|
||||
@@ -139,6 +141,140 @@ impl FieldEntry {
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for FieldEntry {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
let mut s = serializer.serialize_struct("field_entry", 3)?;
|
||||
s.serialize_field("name", &self.name)?;
|
||||
|
||||
match self.field_type {
|
||||
FieldType::Str(ref options) => {
|
||||
s.serialize_field("type", "text")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::U64(ref options) => {
|
||||
s.serialize_field("type", "u64")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::I64(ref options) => {
|
||||
s.serialize_field("type", "i64")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::F64(ref options) => {
|
||||
s.serialize_field("type", "f64")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::Date(ref options) => {
|
||||
s.serialize_field("type", "date")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::HierarchicalFacet(ref options) => {
|
||||
s.serialize_field("type", "hierarchical_facet")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
FieldType::Bytes(ref options) => {
|
||||
s.serialize_field("type", "bytes")?;
|
||||
s.serialize_field("options", options)?;
|
||||
}
|
||||
}
|
||||
|
||||
s.end()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for FieldEntry {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>,
|
||||
{
|
||||
#[derive(Deserialize)]
|
||||
#[serde(field_identifier, rename_all = "lowercase")]
|
||||
enum Field {
|
||||
Name,
|
||||
Type,
|
||||
Options,
|
||||
}
|
||||
|
||||
const FIELDS: &[&str] = &["name", "type", "options"];
|
||||
|
||||
struct FieldEntryVisitor;
|
||||
|
||||
impl<'de> Visitor<'de> for FieldEntryVisitor {
|
||||
type Value = FieldEntry;
|
||||
|
||||
fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("struct FieldEntry")
|
||||
}
|
||||
|
||||
fn visit_map<V>(self, mut map: V) -> Result<FieldEntry, V::Error>
|
||||
where
|
||||
V: MapAccess<'de>,
|
||||
{
|
||||
let mut name = None;
|
||||
let mut ty = None;
|
||||
let mut field_type = None;
|
||||
while let Some(key) = map.next_key()? {
|
||||
match key {
|
||||
Field::Name => {
|
||||
if name.is_some() {
|
||||
return Err(de::Error::duplicate_field("name"));
|
||||
}
|
||||
name = Some(map.next_value()?);
|
||||
}
|
||||
Field::Type => {
|
||||
if ty.is_some() {
|
||||
return Err(de::Error::duplicate_field("type"));
|
||||
}
|
||||
let type_string = map.next_value::<String>()?;
|
||||
match type_string.as_str() {
|
||||
"text" | "u64" | "i64" | "f64" | "date" | "bytes"
|
||||
| "hierarchical_facet" => {
|
||||
// These types require additional options to create a field_type
|
||||
}
|
||||
_ => panic!("unhandled type"),
|
||||
}
|
||||
ty = Some(type_string);
|
||||
}
|
||||
Field::Options => match ty {
|
||||
None => {
|
||||
let msg = "The `type` field must be \
|
||||
specified before `options`";
|
||||
return Err(de::Error::custom(msg));
|
||||
}
|
||||
Some(ref ty) => match ty.as_str() {
|
||||
"text" => field_type = Some(FieldType::Str(map.next_value()?)),
|
||||
"u64" => field_type = Some(FieldType::U64(map.next_value()?)),
|
||||
"i64" => field_type = Some(FieldType::I64(map.next_value()?)),
|
||||
"f64" => field_type = Some(FieldType::F64(map.next_value()?)),
|
||||
"date" => field_type = Some(FieldType::Date(map.next_value()?)),
|
||||
"bytes" => field_type = Some(FieldType::Bytes(map.next_value()?)),
|
||||
"hierarchical_facet" => {
|
||||
field_type =
|
||||
Some(FieldType::HierarchicalFacet(map.next_value()?))
|
||||
}
|
||||
_ => {
|
||||
let msg = format!("Unrecognised type {}", ty);
|
||||
return Err(de::Error::custom(msg));
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
|
||||
ty.ok_or_else(|| de::Error::missing_field("ty"))?;
|
||||
let field_type = field_type.ok_or_else(|| de::Error::missing_field("options"))?;
|
||||
|
||||
Ok(FieldEntry { name, field_type })
|
||||
}
|
||||
}
|
||||
|
||||
deserializer.deserialize_struct("field_entry", FIELDS, FieldEntryVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -179,24 +315,4 @@ mod tests {
|
||||
_ => panic!("expected FieldType::Str"),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_json_deserialization() {
|
||||
let json_str = r#"{
|
||||
"name": "title",
|
||||
"options": {
|
||||
"indexing": {
|
||||
"record": "position",
|
||||
"tokenizer": "default"
|
||||
},
|
||||
"stored": false
|
||||
},
|
||||
"type": "text"
|
||||
}"#;
|
||||
let field_entry: FieldEntry = serde_json::from_str(json_str).unwrap();
|
||||
match field_entry.field_type {
|
||||
FieldType::Str(_) => {}
|
||||
_ => panic!("expected FieldType::Str"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::schema::Value;
|
||||
use crate::schema::{IntOptions, TextOptions};
|
||||
use crate::tokenizer::PreTokenizedString;
|
||||
use chrono::{FixedOffset, Utc};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
|
||||
/// Possible error that may occur while parsing a field value
|
||||
@@ -49,12 +48,9 @@ pub enum Type {
|
||||
|
||||
/// A `FieldType` describes the type (text, u64) of a field as well as
|
||||
/// how it should be handled by tantivy.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(tag = "type", content = "options")]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
pub enum FieldType {
|
||||
/// String field type configuration
|
||||
#[serde(rename = "text")]
|
||||
Str(TextOptions),
|
||||
/// Unsigned 64-bits integers field type configuration
|
||||
U64(IntOptions),
|
||||
|
||||
@@ -15,7 +15,7 @@ pub enum Cardinality {
|
||||
}
|
||||
|
||||
/// Define how an u64, i64, of f64 field should be handled by tantivy.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub struct IntOptions {
|
||||
indexed: bool,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@@ -79,6 +79,16 @@ impl IntOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for IntOptions {
|
||||
fn default() -> IntOptions {
|
||||
IntOptions {
|
||||
indexed: false,
|
||||
stored: false,
|
||||
fast: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<()> for IntOptions {
|
||||
fn from(_: ()) -> IntOptions {
|
||||
IntOptions::default()
|
||||
|
||||
@@ -271,6 +271,8 @@ impl Schema {
|
||||
let field_value = FieldValue::new(field, value);
|
||||
document.add(field_value);
|
||||
}
|
||||
} else {
|
||||
return Err(DocParsingError::NoSuchFieldInSchema(field_name));
|
||||
}
|
||||
}
|
||||
Ok(document)
|
||||
@@ -312,25 +314,26 @@ impl Schema {
|
||||
|
||||
let mut doc = Document::default();
|
||||
for (field_name, json_value) in json_obj.iter() {
|
||||
if let Some(field) = self.get_field(field_name) {
|
||||
let field_entry = self.get_field_entry(field);
|
||||
let field_type = field_entry.field_type();
|
||||
match *json_value {
|
||||
JsonValue::Array(ref json_items) => {
|
||||
for json_item in json_items {
|
||||
let value = field_type
|
||||
.value_from_json(json_item)
|
||||
.map_err(|e| DocParsingError::ValueError(field_name.clone(), e))?;
|
||||
doc.add(FieldValue::new(field, value));
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
let field = self
|
||||
.get_field(field_name)
|
||||
.ok_or_else(|| DocParsingError::NoSuchFieldInSchema(field_name.clone()))?;
|
||||
let field_entry = self.get_field_entry(field);
|
||||
let field_type = field_entry.field_type();
|
||||
match *json_value {
|
||||
JsonValue::Array(ref json_items) => {
|
||||
for json_item in json_items {
|
||||
let value = field_type
|
||||
.value_from_json(json_value)
|
||||
.value_from_json(json_item)
|
||||
.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));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(doc)
|
||||
@@ -395,6 +398,9 @@ pub enum DocParsingError {
|
||||
/// One of the value node could not be parsed.
|
||||
#[error("The field '{0:?}' could not be parsed: {1:?}")]
|
||||
ValueError(String, ValueParsingError),
|
||||
/// The json-document contains a field that is not declared in the schema.
|
||||
#[error("The document contains a field that is not declared in the schema: {0:?}")]
|
||||
NoSuchFieldInSchema(String),
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -572,16 +578,20 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
pub fn test_document_missing_field_no_error() {
|
||||
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")],
|
||||
);
|
||||
schema
|
||||
let err = schema
|
||||
.convert_named_doc(NamedFieldDocument(named_doc_map))
|
||||
.unwrap();
|
||||
.unwrap_err();
|
||||
assert_eq!(
|
||||
err,
|
||||
DocParsingError::NoSuchFieldInSchema("title".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -634,9 +644,8 @@ mod tests {
|
||||
);
|
||||
}
|
||||
{
|
||||
let res = schema.parse_document(
|
||||
let json_err = schema.parse_document(
|
||||
r#"{
|
||||
"thisfieldisnotdefinedintheschema": "my title",
|
||||
"title": "my title",
|
||||
"author": "fulmicoton",
|
||||
"count": 4,
|
||||
@@ -645,7 +654,7 @@ mod tests {
|
||||
"jambon": "bayonne"
|
||||
}"#,
|
||||
);
|
||||
assert!(res.is_ok());
|
||||
assert_matches!(json_err, Err(DocParsingError::NoSuchFieldInSchema(_)));
|
||||
}
|
||||
{
|
||||
let json_err = schema.parse_document(
|
||||
|
||||
@@ -6,7 +6,7 @@ use std::borrow::Cow;
|
||||
use std::ops::BitOr;
|
||||
|
||||
/// Define how a text field should be handled by tantivy.
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default)]
|
||||
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct TextOptions {
|
||||
indexing: Option<TextFieldIndexing>,
|
||||
stored: bool,
|
||||
@@ -36,6 +36,15 @@ impl TextOptions {
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for TextOptions {
|
||||
fn default() -> TextOptions {
|
||||
TextOptions {
|
||||
indexing: None,
|
||||
stored: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration defining indexing for a text field.
|
||||
///
|
||||
/// It defines
|
||||
|
||||
@@ -16,6 +16,7 @@ pub struct FragmentCandidate {
|
||||
score: Score,
|
||||
start_offset: usize,
|
||||
stop_offset: usize,
|
||||
num_chars: usize,
|
||||
highlighted: Vec<Range<usize>>,
|
||||
}
|
||||
|
||||
@@ -30,6 +31,7 @@ impl FragmentCandidate {
|
||||
score: 0.0,
|
||||
start_offset,
|
||||
stop_offset: start_offset,
|
||||
num_chars: 0,
|
||||
highlighted: vec![],
|
||||
}
|
||||
}
|
||||
@@ -207,7 +209,7 @@ fn select_best_fragment_combination(fragments: &[FragmentCandidate], text: &str)
|
||||
/// # Quand avec mes haleurs ont fini ces tapages,
|
||||
/// # Les Fleuves m'ont laissé descendre où je voulais.
|
||||
/// # "#);
|
||||
/// # index_writer.add_document(doc.clone())?;
|
||||
/// # index_writer.add_document(doc.clone());
|
||||
/// # index_writer.commit()?;
|
||||
/// # let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
/// // ...
|
||||
@@ -470,55 +472,58 @@ Survey in 2016, 2017, and 2018."#;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_generator_term_score() -> crate::Result<()> {
|
||||
fn test_snippet_generator_term_score() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_field = schema_builder.add_text_field("text", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field => "a"))?;
|
||||
index_writer.add_document(doc!(text_field => "a"))?;
|
||||
index_writer.add_document(doc!(text_field => "a b"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
index_writer.add_document(doc!(text_field => "a"));
|
||||
index_writer.add_document(doc!(text_field => "a b"));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
{
|
||||
let query = query_parser.parse_query("e")?;
|
||||
let snippet_generator = SnippetGenerator::create(&searcher, &*query, text_field)?;
|
||||
let query = query_parser.parse_query("e").unwrap();
|
||||
let snippet_generator =
|
||||
SnippetGenerator::create(&searcher, &*query, text_field).unwrap();
|
||||
assert!(snippet_generator.terms_text().is_empty());
|
||||
}
|
||||
{
|
||||
let query = query_parser.parse_query("a")?;
|
||||
let snippet_generator = SnippetGenerator::create(&searcher, &*query, text_field)?;
|
||||
let query = query_parser.parse_query("a").unwrap();
|
||||
let snippet_generator =
|
||||
SnippetGenerator::create(&searcher, &*query, text_field).unwrap();
|
||||
assert_eq!(
|
||||
&btreemap!("a".to_string() => 0.25),
|
||||
snippet_generator.terms_text()
|
||||
);
|
||||
}
|
||||
{
|
||||
let query = query_parser.parse_query("a b")?;
|
||||
let snippet_generator = SnippetGenerator::create(&searcher, &*query, text_field)?;
|
||||
let query = query_parser.parse_query("a b").unwrap();
|
||||
let snippet_generator =
|
||||
SnippetGenerator::create(&searcher, &*query, text_field).unwrap();
|
||||
assert_eq!(
|
||||
&btreemap!("a".to_string() => 0.25, "b".to_string() => 0.5),
|
||||
snippet_generator.terms_text()
|
||||
);
|
||||
}
|
||||
{
|
||||
let query = query_parser.parse_query("a b c")?;
|
||||
let snippet_generator = SnippetGenerator::create(&searcher, &*query, text_field)?;
|
||||
let query = query_parser.parse_query("a b c").unwrap();
|
||||
let snippet_generator =
|
||||
SnippetGenerator::create(&searcher, &*query, text_field).unwrap();
|
||||
assert_eq!(
|
||||
&btreemap!("a".to_string() => 0.25, "b".to_string() => 0.5),
|
||||
snippet_generator.terms_text()
|
||||
);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_snippet_generator() -> crate::Result<()> {
|
||||
fn test_snippet_generator() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let text_options = TextOptions::default().set_indexing_options(
|
||||
TextFieldIndexing::default()
|
||||
@@ -530,10 +535,12 @@ Survey in 2016, 2017, and 2018."#;
|
||||
let index = Index::create_in_ram(schema);
|
||||
{
|
||||
// writing the segment
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
let doc = doc!(text_field => TEST_TEXT);
|
||||
index_writer.add_document(doc)?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
{
|
||||
let doc = doc ! (text_field => TEST_TEXT);
|
||||
index_writer.add_document(doc);
|
||||
}
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let query_parser = QueryParser::for_index(&index, vec![text_field]);
|
||||
@@ -549,6 +556,5 @@ Survey in 2016, 2017, and 2018."#;
|
||||
let snippet = snippet_generator.snippet(TEST_TEXT);
|
||||
assert_eq!(snippet.to_html(), "<b>Rust</b> is syntactically similar to C++[according to whom?],\nbut its <b>designers</b> intend it to");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,24 +321,24 @@ mod test {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_fast_indexed() -> crate::Result<()> {
|
||||
fn test_fast_indexed() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let name = schema_builder.add_u64_field("name", FAST | INDEXED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => 1u64))?;
|
||||
index_writer.add_document(doc!(name => 2u64))?;
|
||||
index_writer.add_document(doc!(name => 10u64))?;
|
||||
index_writer.add_document(doc!(name => 20u64))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(name => 1u64));
|
||||
index_writer.add_document(doc!(name => 2u64));
|
||||
index_writer.add_document(doc!(name => 10u64));
|
||||
index_writer.add_document(doc!(name => 20u64));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let searcher_space_usage = searcher.space_usage()?;
|
||||
let searcher_space_usage = searcher.space_usage().unwrap();
|
||||
assert!(searcher_space_usage.total() > 0);
|
||||
assert_eq!(1, searcher_space_usage.segments().len());
|
||||
|
||||
@@ -355,30 +355,29 @@ mod test {
|
||||
// TODO: understand why the following fails
|
||||
// assert_eq!(0, segment.store().total());
|
||||
assert_eq!(0, segment.deletes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_text() -> crate::Result<()> {
|
||||
fn test_text() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let name = schema_builder.add_text_field("name", TEXT);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => "hi"))?;
|
||||
index_writer.add_document(doc!(name => "this is a test"))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(name => "hi"));
|
||||
index_writer.add_document(doc!(name => "this is a test"));
|
||||
index_writer.add_document(
|
||||
doc!(name => "some more documents with some word overlap with the other test"),
|
||||
)?;
|
||||
index_writer.add_document(doc!(name => "hello hi goodbye"))?;
|
||||
index_writer.commit()?;
|
||||
);
|
||||
index_writer.add_document(doc!(name => "hello hi goodbye"));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let searcher_space_usage = searcher.space_usage()?;
|
||||
let searcher_space_usage = searcher.space_usage().unwrap();
|
||||
assert!(searcher_space_usage.total() > 0);
|
||||
assert_eq!(1, searcher_space_usage.segments().len());
|
||||
|
||||
@@ -395,29 +394,28 @@ mod test {
|
||||
// TODO: understand why the following fails
|
||||
// assert_eq!(0, segment.store().total());
|
||||
assert_eq!(0, segment.deletes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store() -> crate::Result<()> {
|
||||
fn test_store() {
|
||||
let mut schema_builder = Schema::builder();
|
||||
let name = schema_builder.add_text_field("name", STORED);
|
||||
let schema = schema_builder.build();
|
||||
let index = Index::create_in_ram(schema);
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => "hi"))?;
|
||||
index_writer.add_document(doc!(name => "this is a test"))?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(name => "hi"));
|
||||
index_writer.add_document(doc!(name => "this is a test"));
|
||||
index_writer.add_document(
|
||||
doc!(name => "some more documents with some word overlap with the other test"),
|
||||
)?;
|
||||
index_writer.add_document(doc!(name => "hello hi goodbye"))?;
|
||||
index_writer.commit()?;
|
||||
);
|
||||
index_writer.add_document(doc!(name => "hello hi goodbye"));
|
||||
index_writer.commit().unwrap();
|
||||
}
|
||||
let reader = index.reader()?;
|
||||
let reader = index.reader().unwrap();
|
||||
let searcher = reader.searcher();
|
||||
let searcher_space_usage = searcher.space_usage()?;
|
||||
let searcher_space_usage = searcher.space_usage().unwrap();
|
||||
assert!(searcher_space_usage.total() > 0);
|
||||
assert_eq!(1, searcher_space_usage.segments().len());
|
||||
|
||||
@@ -434,7 +432,6 @@ mod test {
|
||||
assert!(segment.store().total() > 0);
|
||||
assert!(segment.store().total() < 512);
|
||||
assert_eq!(0, segment.deletes());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -446,10 +443,10 @@ mod test {
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(name => 1u64))?;
|
||||
index_writer.add_document(doc!(name => 2u64))?;
|
||||
index_writer.add_document(doc!(name => 3u64))?;
|
||||
index_writer.add_document(doc!(name => 4u64))?;
|
||||
index_writer.add_document(doc!(name => 1u64));
|
||||
index_writer.add_document(doc!(name => 2u64));
|
||||
index_writer.add_document(doc!(name => 3u64));
|
||||
index_writer.add_document(doc!(name => 4u64));
|
||||
index_writer.commit()?;
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,6 @@ pub trait StoreCompressor {
|
||||
/// The default is Lz4Block, but also depends on the enabled feature flags.
|
||||
#[derive(Clone, Debug, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum Compressor {
|
||||
#[serde(rename = "none")]
|
||||
/// No compression
|
||||
None,
|
||||
#[serde(rename = "lz4")]
|
||||
/// Use the lz4 compressor (block format)
|
||||
Lz4,
|
||||
@@ -36,7 +33,9 @@ impl Default for Compressor {
|
||||
} else if cfg!(feature = "snappy-compression") {
|
||||
Compressor::Snappy
|
||||
} else {
|
||||
Compressor::None
|
||||
panic!(
|
||||
"all compressor feature flags like are disabled (e.g. lz4-compression), can't choose default compressor"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,7 +43,6 @@ impl Default for Compressor {
|
||||
impl Compressor {
|
||||
pub(crate) fn from_id(id: u8) -> Compressor {
|
||||
match id {
|
||||
0 => Compressor::None,
|
||||
1 => Compressor::Lz4,
|
||||
2 => Compressor::Brotli,
|
||||
3 => Compressor::Snappy,
|
||||
@@ -53,7 +51,6 @@ impl Compressor {
|
||||
}
|
||||
pub(crate) fn get_id(&self) -> u8 {
|
||||
match self {
|
||||
Self::None => 0,
|
||||
Self::Lz4 => 1,
|
||||
Self::Brotli => 2,
|
||||
Self::Snappy => 3,
|
||||
@@ -62,11 +59,6 @@ impl Compressor {
|
||||
#[inline]
|
||||
pub(crate) fn compress(&self, uncompressed: &[u8], compressed: &mut Vec<u8>) -> io::Result<()> {
|
||||
match self {
|
||||
Self::None => {
|
||||
compressed.clear();
|
||||
compressed.extend_from_slice(uncompressed);
|
||||
Ok(())
|
||||
}
|
||||
Self::Lz4 => {
|
||||
#[cfg(feature = "lz4-compression")]
|
||||
{
|
||||
@@ -107,11 +99,6 @@ impl Compressor {
|
||||
decompressed: &mut Vec<u8>,
|
||||
) -> io::Result<()> {
|
||||
match self {
|
||||
Self::None => {
|
||||
decompressed.clear();
|
||||
decompressed.extend_from_slice(compressed);
|
||||
Ok(())
|
||||
}
|
||||
Self::Lz4 => {
|
||||
#[cfg(feature = "lz4-compression")]
|
||||
{
|
||||
|
||||
@@ -136,12 +136,12 @@ mod tests {
|
||||
index_writer.set_merge_policy(Box::new(NoMergePolicy));
|
||||
let long_text: String = "abcdefghijklmnopqrstuvwxyz".repeat(1_000);
|
||||
for _ in 0..20 {
|
||||
index_writer.add_document(doc!(body=>long_text.clone()))?;
|
||||
index_writer.add_document(doc!(body=>long_text.clone()));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text=>"testb"))?;
|
||||
index_writer.add_document(doc!(text=>"testb"));
|
||||
for _ in 0..10 {
|
||||
index_writer.add_document(doc!(text=>"testd", body=>long_text.clone()))?;
|
||||
index_writer.add_document(doc!(text=>"testd", body=>long_text.clone()));
|
||||
}
|
||||
index_writer.commit()?;
|
||||
index_writer.delete_term(Term::from_field_text(text, "testb"));
|
||||
|
||||
@@ -112,9 +112,9 @@ pub mod tests {
|
||||
#[test]
|
||||
fn test_doc_store_iter_with_delete_bug_1077() -> crate::Result<()> {
|
||||
// this will cover deletion of the first element in a checkpoint
|
||||
let deleted_doc_ids = (200..300).collect::<Vec<_>>();
|
||||
let deleted_docids = (200..300).collect::<Vec<_>>();
|
||||
let alive_bitset =
|
||||
AliveBitSet::for_test_from_deleted_docs(&deleted_doc_ids, NUM_DOCS as u32);
|
||||
AliveBitSet::for_test_from_deleted_docs(&deleted_docids, NUM_DOCS as u32);
|
||||
|
||||
let path = Path::new("store");
|
||||
let directory = RamDirectory::create();
|
||||
@@ -183,10 +183,6 @@ pub mod tests {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_store_noop() -> crate::Result<()> {
|
||||
test_store(Compressor::None)
|
||||
}
|
||||
#[cfg(feature = "lz4-compression")]
|
||||
#[test]
|
||||
fn test_store_lz4_block() -> crate::Result<()> {
|
||||
@@ -217,23 +213,24 @@ pub mod tests {
|
||||
let schema = schema_builder.build();
|
||||
let index_builder = Index::builder().schema(schema);
|
||||
|
||||
let index = index_builder.create_in_ram()?;
|
||||
let index = index_builder.create_in_ram().unwrap();
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"))?;
|
||||
index_writer.add_document(doc!(text_field=> "deletemenot"))?;
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"))?;
|
||||
index_writer.add_document(doc!(text_field=> "deletemenot"))?;
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"))?;
|
||||
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"));
|
||||
index_writer.add_document(doc!(text_field=> "deletemenot"));
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"));
|
||||
index_writer.add_document(doc!(text_field=> "deletemenot"));
|
||||
index_writer.add_document(doc!(text_field=> "deleteme"));
|
||||
|
||||
index_writer.delete_term(Term::from_field_text(text_field, "deleteme"));
|
||||
index_writer.commit()?;
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
let reader = searcher.segment_reader(0);
|
||||
let store = reader.get_store_reader()?;
|
||||
let store = reader.get_store_reader().unwrap();
|
||||
for doc in store.iter(reader.alive_bitset()) {
|
||||
assert_eq!(
|
||||
*doc?.get_first(text_field).unwrap().text().unwrap(),
|
||||
@@ -314,30 +311,33 @@ pub mod tests {
|
||||
let index = index_builder.create_in_ram().unwrap();
|
||||
|
||||
{
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
index_writer.add_document(doc!(text_field=> "1"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=> "2"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=> "3"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=> "4"))?;
|
||||
index_writer.commit()?;
|
||||
index_writer.add_document(doc!(text_field=> "5"))?;
|
||||
index_writer.commit()?;
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
|
||||
index_writer.add_document(doc!(text_field=> "1"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc!(text_field=> "2"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc!(text_field=> "3"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc!(text_field=> "4"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
index_writer.add_document(doc!(text_field=> "5"));
|
||||
assert!(index_writer.commit().is_ok());
|
||||
}
|
||||
// Merging the segments
|
||||
{
|
||||
let segment_ids = index.searchable_segment_ids()?;
|
||||
let mut index_writer = index.writer_for_tests()?;
|
||||
block_on(index_writer.merge(&segment_ids))?;
|
||||
index_writer.wait_merging_threads()?;
|
||||
let segment_ids = index
|
||||
.searchable_segment_ids()
|
||||
.expect("Searchable segments failed.");
|
||||
let mut index_writer = index.writer_for_tests().unwrap();
|
||||
assert!(block_on(index_writer.merge(&segment_ids)).is_ok());
|
||||
assert!(index_writer.wait_merging_threads().is_ok());
|
||||
}
|
||||
|
||||
let searcher = index.reader()?.searcher();
|
||||
let searcher = index.reader().unwrap().searcher();
|
||||
assert_eq!(searcher.segment_readers().len(), 1);
|
||||
let reader = searcher.segment_readers().iter().last().unwrap();
|
||||
let store = reader.get_store_reader()?;
|
||||
let store = reader.get_store_reader().unwrap();
|
||||
assert_eq!(store.block_checkpoints().count(), 1);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ impl StoreReader {
|
||||
&'b self,
|
||||
alive_bitset: Option<&'a AliveBitSet>,
|
||||
) -> impl Iterator<Item = crate::Result<OwnedBytes>> + 'b {
|
||||
let last_doc_id = self
|
||||
let last_docid = self
|
||||
.block_checkpoints()
|
||||
.last()
|
||||
.map(|checkpoint| checkpoint.doc_range.end)
|
||||
@@ -164,7 +164,7 @@ impl StoreReader {
|
||||
let mut block_start_pos = 0;
|
||||
let mut num_skipped = 0;
|
||||
let mut reset_block_pos = false;
|
||||
(0..last_doc_id)
|
||||
(0..last_docid)
|
||||
.filter_map(move |doc_id| {
|
||||
// filter_map is only used to resolve lifetime issues between the two closures on
|
||||
// the outer variables
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user