mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-07 01:32:53 +00:00
Compare commits
15 Commits
segment_wr
...
cutt
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
95f1114870 | ||
|
|
186d7fc20e | ||
|
|
cfbdef5186 | ||
|
|
d04368b1d4 | ||
|
|
b167058028 | ||
|
|
262957717b | ||
|
|
873a808321 | ||
|
|
6fa8f9330e | ||
|
|
b3f0ef0878 | ||
|
|
04304262ba | ||
|
|
920ced364a | ||
|
|
e0499118e2 | ||
|
|
50b5efae46 | ||
|
|
486b8fa9c5 | ||
|
|
b2baed9bdd |
@@ -1,6 +1,6 @@
|
|||||||
Tantivy 0.12.1
|
Tantivy 0.13.0
|
||||||
=====================
|
======================
|
||||||
- By default IndexReader are in `Manual` mode.
|
- Bugfix in `FuzzyTermQuery` not matching terms by prefix when it should (@Peachball)
|
||||||
|
|
||||||
Tantivy 0.12.0
|
Tantivy 0.12.0
|
||||||
======================
|
======================
|
||||||
|
|||||||
12
Cargo.toml
12
Cargo.toml
@@ -13,25 +13,23 @@ keywords = ["search", "information", "retrieval"]
|
|||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
base64 = "0.11.0"
|
base64 = "0.12.0"
|
||||||
byteorder = "1.0"
|
byteorder = "1.0"
|
||||||
crc32fast = "1.2.0"
|
crc32fast = "1.2.0"
|
||||||
once_cell = "1.0"
|
once_cell = "1.0"
|
||||||
regex ={version = "1.3.0", default-features = false, features = ["std"]}
|
regex ={version = "1.3.0", default-features = false, features = ["std"]}
|
||||||
tantivy-fst = "0.2.1"
|
tantivy-fst = {path="../tantivy-fst", version="0.3"}
|
||||||
memmap = {version = "0.7", optional=true}
|
memmap = {version = "0.7", optional=true}
|
||||||
lz4 = {version="1.20", optional=true}
|
lz4 = {version="1.20", optional=true}
|
||||||
snap = "1"
|
snap = "1"
|
||||||
atomicwrites = {version="0.2.2", optional=true}
|
atomicwrites = {version="0.2.2", optional=true}
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
serde = "1.0"
|
serde = {version="1.0", features=["derive"]}
|
||||||
serde_derive = "1.0"
|
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
num_cpus = "1.2"
|
num_cpus = "1.2"
|
||||||
fs2={version="0.4", optional=true}
|
fs2={version="0.4", optional=true}
|
||||||
itertools = "0.8"
|
levenshtein_automata = "0.2"
|
||||||
levenshtein_automata = "0.1"
|
|
||||||
notify = {version="4", optional=true}
|
notify = {version="4", optional=true}
|
||||||
uuid = { version = "0.8", features = ["v4", "serde"] }
|
uuid = { version = "0.8", features = ["v4", "serde"] }
|
||||||
crossbeam = "0.7"
|
crossbeam = "0.7"
|
||||||
@@ -40,7 +38,7 @@ owning_ref = "0.4"
|
|||||||
stable_deref_trait = "1.0.0"
|
stable_deref_trait = "1.0.0"
|
||||||
rust-stemmers = "1.2"
|
rust-stemmers = "1.2"
|
||||||
downcast-rs = { version="1.0" }
|
downcast-rs = { version="1.0" }
|
||||||
tantivy-query-grammar = { version="0.12", path="./query-grammar" }
|
tantivy-query-grammar = { version="0.13", path="./query-grammar" }
|
||||||
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
bitpacking = {version="0.8", default-features = false, features=["bitpacker4x"]}
|
||||||
census = "0.4"
|
census = "0.4"
|
||||||
fnv = "1.0.6"
|
fnv = "1.0.6"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ performance for different type of queries / collection.
|
|||||||
# Features
|
# Features
|
||||||
|
|
||||||
- Full-text search
|
- Full-text search
|
||||||
- Configurable tokenizer (stemming available for 17 Latin languages with third party support for Chinese ([tantivy-jieba](https://crates.io/crates/tantivy-jieba) and [cang-jie](https://crates.io/crates/cang-jie)) and [Japanese](https://crates.io/crates/tantivy-tokenizer-tiny-segmenter))
|
- Configurable tokenizer (stemming available for 17 Latin languages with third party support for Chinese ([tantivy-jieba](https://crates.io/crates/tantivy-jieba) and [cang-jie](https://crates.io/crates/cang-jie)), Japanese ([lindera](https://github.com/lindera-morphology/lindera-tantivy) and [tantivy-tokenizer-tiny-segmente](https://crates.io/crates/tantivy-tokenizer-tiny-segmenter)) and Korean ([lindera](https://github.com/lindera-morphology/lindera-tantivy) + [lindera-ko-dic-builder](https://github.com/lindera-morphology/lindera-ko-dic-builder))
|
||||||
- Fast (check out the :racehorse: :sparkles: [benchmark](https://tantivy-search.github.io/bench/) :sparkles: :racehorse:)
|
- Fast (check out the :racehorse: :sparkles: [benchmark](https://tantivy-search.github.io/bench/) :sparkles: :racehorse:)
|
||||||
- Tiny startup time (<10ms), perfect for command line tools
|
- Tiny startup time (<10ms), perfect for command line tools
|
||||||
- BM25 scoring (the same as Lucene)
|
- BM25 scoring (the same as Lucene)
|
||||||
|
|||||||
@@ -18,5 +18,5 @@ install:
|
|||||||
build: false
|
build: false
|
||||||
|
|
||||||
test_script:
|
test_script:
|
||||||
- REM SET RUST_LOG=tantivy,test & cargo test --verbose --no-default-features --features mmap
|
- REM SET RUST_LOG=tantivy,test & cargo test --all --verbose --no-default-features --features mmap
|
||||||
- REM SET RUST_BACKTRACE=1 & cargo build --examples
|
- REM SET RUST_BACKTRACE=1 & cargo build --examples
|
||||||
|
|||||||
@@ -9,11 +9,10 @@
|
|||||||
// - import tokenized text straight from json,
|
// - import tokenized text straight from json,
|
||||||
// - perform a search on documents with pre-tokenized text
|
// - perform a search on documents with pre-tokenized text
|
||||||
|
|
||||||
use tantivy::tokenizer::{PreTokenizedString, SimpleTokenizer, Token, Tokenizer};
|
|
||||||
|
|
||||||
use tantivy::collector::{Count, TopDocs};
|
use tantivy::collector::{Count, TopDocs};
|
||||||
use tantivy::query::TermQuery;
|
use tantivy::query::TermQuery;
|
||||||
use tantivy::schema::*;
|
use tantivy::schema::*;
|
||||||
|
use tantivy::tokenizer::{PreTokenizedString, SimpleTokenizer, Token, Tokenizer};
|
||||||
use tantivy::{doc, Index, ReloadPolicy};
|
use tantivy::{doc, Index, ReloadPolicy};
|
||||||
use tempfile::TempDir;
|
use tempfile::TempDir;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "tantivy-query-grammar"
|
name = "tantivy-query-grammar"
|
||||||
version = "0.12.0"
|
version = "0.13.0"
|
||||||
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
authors = ["Paul Masurel <paul.masurel@gmail.com>"]
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
categories = ["database-implementations", "data-structures"]
|
categories = ["database-implementations", "data-structures"]
|
||||||
|
|||||||
@@ -154,17 +154,11 @@ fn negate(expr: UserInputAST) -> UserInputAST {
|
|||||||
expr.unary(Occur::MustNot)
|
expr.unary(Occur::MustNot)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn must(expr: UserInputAST) -> UserInputAST {
|
|
||||||
expr.unary(Occur::Must)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn leaf<'a>() -> impl Parser<&'a str, Output = UserInputAST> {
|
fn leaf<'a>() -> impl Parser<&'a str, Output = UserInputAST> {
|
||||||
parser(|input| {
|
parser(|input| {
|
||||||
char('-')
|
char('(')
|
||||||
.with(leaf())
|
.with(ast())
|
||||||
.map(negate)
|
.skip(char(')'))
|
||||||
.or(char('+').with(leaf()).map(must))
|
|
||||||
.or(char('(').with(ast()).skip(char(')')))
|
|
||||||
.or(char('*').map(|_| UserInputAST::from(UserInputLeaf::All)))
|
.or(char('*').map(|_| UserInputAST::from(UserInputLeaf::All)))
|
||||||
.or(attempt(
|
.or(attempt(
|
||||||
string("NOT").skip(spaces1()).with(leaf()).map(negate),
|
string("NOT").skip(spaces1()).with(leaf()).map(negate),
|
||||||
@@ -176,6 +170,16 @@ fn leaf<'a>() -> impl Parser<&'a str, Output = UserInputAST> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn occur_symbol<'a>() -> impl Parser<&'a str, Output = Occur> {
|
||||||
|
char('-')
|
||||||
|
.map(|_| Occur::MustNot)
|
||||||
|
.or(char('+').map(|_| Occur::Must))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn occur_leaf<'a>() -> impl Parser<&'a str, Output = (Option<Occur>, UserInputAST)> {
|
||||||
|
(optional(occur_symbol()), boosted_leaf())
|
||||||
|
}
|
||||||
|
|
||||||
fn positive_float_number<'a>() -> impl Parser<&'a str, Output = f32> {
|
fn positive_float_number<'a>() -> impl Parser<&'a str, Output = f32> {
|
||||||
(many1(digit()), optional((char('.'), many1(digit())))).map(
|
(many1(digit()), optional((char('.'), many1(digit())))).map(
|
||||||
|(int_part, decimal_part_opt): (String, Option<(char, String)>)| {
|
|(int_part, decimal_part_opt): (String, Option<(char, String)>)| {
|
||||||
@@ -239,21 +243,29 @@ fn aggregate_binary_expressions(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ast<'a>() -> impl Parser<&'a str, Output = UserInputAST> {
|
fn operand_leaf<'a>() -> impl Parser<&'a str, Output = (BinaryOperand, UserInputAST)> {
|
||||||
let operand_leaf = (
|
(
|
||||||
binary_operand().skip(spaces()),
|
binary_operand().skip(spaces()),
|
||||||
boosted_leaf().skip(spaces()),
|
boosted_leaf().skip(spaces()),
|
||||||
);
|
)
|
||||||
let boolean_expr = (boosted_leaf().skip(spaces().silent()), many1(operand_leaf))
|
}
|
||||||
|
|
||||||
|
pub fn ast<'a>() -> impl Parser<&'a str, Output = UserInputAST> {
|
||||||
|
let boolean_expr = (boosted_leaf().skip(spaces()), many1(operand_leaf()))
|
||||||
.map(|(left, right)| aggregate_binary_expressions(left, right));
|
.map(|(left, right)| aggregate_binary_expressions(left, right));
|
||||||
let whitespace_separated_leaves =
|
let whitespace_separated_leaves = many1(occur_leaf().skip(spaces().silent())).map(
|
||||||
many1(boosted_leaf().skip(spaces().silent())).map(|subqueries: Vec<UserInputAST>| {
|
|subqueries: Vec<(Option<Occur>, UserInputAST)>| {
|
||||||
if subqueries.len() == 1 {
|
if subqueries.len() == 1 {
|
||||||
subqueries.into_iter().next().unwrap()
|
let (occur_opt, ast) = subqueries.into_iter().next().unwrap();
|
||||||
|
match occur_opt.unwrap_or(Occur::Should) {
|
||||||
|
Occur::Must | Occur::Should => ast,
|
||||||
|
Occur::MustNot => UserInputAST::Clause(vec![(Some(Occur::MustNot), ast)]),
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
UserInputAST::Clause(subqueries.into_iter().collect())
|
UserInputAST::Clause(subqueries.into_iter().collect())
|
||||||
}
|
}
|
||||||
});
|
},
|
||||||
|
);
|
||||||
let expr = attempt(boolean_expr).or(whitespace_separated_leaves);
|
let expr = attempt(boolean_expr).or(whitespace_separated_leaves);
|
||||||
spaces().with(expr).skip(spaces())
|
spaces().with(expr).skip(spaces())
|
||||||
}
|
}
|
||||||
@@ -283,6 +295,12 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_occur_symbol() {
|
||||||
|
assert_eq!(super::occur_symbol().parse("-"), Ok((Occur::MustNot, "")));
|
||||||
|
assert_eq!(super::occur_symbol().parse("+"), Ok((Occur::Must, "")));
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_positive_float_number() {
|
fn test_positive_float_number() {
|
||||||
fn valid_parse(float_str: &str, expected_val: f32, expected_remaining: &str) {
|
fn valid_parse(float_str: &str, expected_val: f32, expected_remaining: &str) {
|
||||||
@@ -330,7 +348,7 @@ mod test {
|
|||||||
"Err(UnexpectedParse)"
|
"Err(UnexpectedParse)"
|
||||||
);
|
);
|
||||||
test_parse_query_to_ast_helper("NOTa", "\"NOTa\"");
|
test_parse_query_to_ast_helper("NOTa", "\"NOTa\"");
|
||||||
test_parse_query_to_ast_helper("NOT a", "-(\"a\")");
|
test_parse_query_to_ast_helper("NOT a", "(-\"a\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -338,16 +356,16 @@ mod test {
|
|||||||
assert!(parse_to_ast().parse("a^2^3").is_err());
|
assert!(parse_to_ast().parse("a^2^3").is_err());
|
||||||
assert!(parse_to_ast().parse("a^2^").is_err());
|
assert!(parse_to_ast().parse("a^2^").is_err());
|
||||||
test_parse_query_to_ast_helper("a^3", "(\"a\")^3");
|
test_parse_query_to_ast_helper("a^3", "(\"a\")^3");
|
||||||
test_parse_query_to_ast_helper("a^3 b^2", "((\"a\")^3 (\"b\")^2)");
|
test_parse_query_to_ast_helper("a^3 b^2", "(*(\"a\")^3 *(\"b\")^2)");
|
||||||
test_parse_query_to_ast_helper("a^1", "\"a\"");
|
test_parse_query_to_ast_helper("a^1", "\"a\"");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_query_to_ast_binary_op() {
|
fn test_parse_query_to_ast_binary_op() {
|
||||||
test_parse_query_to_ast_helper("a AND b", "(+(\"a\") +(\"b\"))");
|
test_parse_query_to_ast_helper("a AND b", "(+\"a\" +\"b\")");
|
||||||
test_parse_query_to_ast_helper("a OR b", "(?(\"a\") ?(\"b\"))");
|
test_parse_query_to_ast_helper("a OR b", "(?\"a\" ?\"b\")");
|
||||||
test_parse_query_to_ast_helper("a OR b AND c", "(?(\"a\") ?((+(\"b\") +(\"c\"))))");
|
test_parse_query_to_ast_helper("a OR b AND c", "(?\"a\" ?(+\"b\" +\"c\"))");
|
||||||
test_parse_query_to_ast_helper("a AND b AND c", "(+(\"a\") +(\"b\") +(\"c\"))");
|
test_parse_query_to_ast_helper("a AND b AND c", "(+\"a\" +\"b\" +\"c\")");
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:?}", parse_to_ast().parse("a OR b aaa")),
|
format!("{:?}", parse_to_ast().parse("a OR b aaa")),
|
||||||
"Err(UnexpectedParse)"
|
"Err(UnexpectedParse)"
|
||||||
@@ -385,6 +403,13 @@ mod test {
|
|||||||
test_parse_query_to_ast_helper("weight: <= 70.5", "weight:{\"*\" TO \"70.5\"]");
|
test_parse_query_to_ast_helper("weight: <= 70.5", "weight:{\"*\" TO \"70.5\"]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_occur_leaf() {
|
||||||
|
let ((occur, ast), _) = super::occur_leaf().parse("+abc").unwrap();
|
||||||
|
assert_eq!(occur, Some(Occur::Must));
|
||||||
|
assert_eq!(format!("{:?}", ast), "\"abc\"");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_range_parser() {
|
fn test_range_parser() {
|
||||||
// testing the range() parser separately
|
// testing the range() parser separately
|
||||||
@@ -413,32 +438,67 @@ mod test {
|
|||||||
fn test_parse_query_to_triming_spaces() {
|
fn test_parse_query_to_triming_spaces() {
|
||||||
test_parse_query_to_ast_helper(" abc", "\"abc\"");
|
test_parse_query_to_ast_helper(" abc", "\"abc\"");
|
||||||
test_parse_query_to_ast_helper("abc ", "\"abc\"");
|
test_parse_query_to_ast_helper("abc ", "\"abc\"");
|
||||||
test_parse_query_to_ast_helper("( a OR abc)", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("( a OR abc)", "(?\"a\" ?\"abc\")");
|
||||||
test_parse_query_to_ast_helper("(a OR abc)", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("(a OR abc)", "(?\"a\" ?\"abc\")");
|
||||||
test_parse_query_to_ast_helper("(a OR abc)", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("(a OR abc)", "(?\"a\" ?\"abc\")");
|
||||||
test_parse_query_to_ast_helper("a OR abc ", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("a OR abc ", "(?\"a\" ?\"abc\")");
|
||||||
test_parse_query_to_ast_helper("(a OR abc )", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("(a OR abc )", "(?\"a\" ?\"abc\")");
|
||||||
test_parse_query_to_ast_helper("(a OR abc) ", "(?(\"a\") ?(\"abc\"))");
|
test_parse_query_to_ast_helper("(a OR abc) ", "(?\"a\" ?\"abc\")");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_parse_query_to_ast() {
|
fn test_parse_query_single_term() {
|
||||||
test_parse_query_to_ast_helper("abc", "\"abc\"");
|
test_parse_query_to_ast_helper("abc", "\"abc\"");
|
||||||
test_parse_query_to_ast_helper("a b", "(\"a\" \"b\")");
|
}
|
||||||
test_parse_query_to_ast_helper("+(a b)", "+((\"a\" \"b\"))");
|
|
||||||
test_parse_query_to_ast_helper("+d", "+(\"d\")");
|
#[test]
|
||||||
test_parse_query_to_ast_helper("+(a b) +d", "(+((\"a\" \"b\")) +(\"d\"))");
|
fn test_parse_query_default_clause() {
|
||||||
test_parse_query_to_ast_helper("(+a +b) d", "((+(\"a\") +(\"b\")) \"d\")");
|
test_parse_query_to_ast_helper("a b", "(*\"a\" *\"b\")");
|
||||||
test_parse_query_to_ast_helper("(+a)", "+(\"a\")");
|
}
|
||||||
test_parse_query_to_ast_helper("(+a +b)", "(+(\"a\") +(\"b\"))");
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_query_must_default_clause() {
|
||||||
|
test_parse_query_to_ast_helper("+(a b)", "(*\"a\" *\"b\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_query_must_single_term() {
|
||||||
|
test_parse_query_to_ast_helper("+d", "\"d\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_term_with_field() {
|
||||||
test_parse_query_to_ast_helper("abc:toto", "abc:\"toto\"");
|
test_parse_query_to_ast_helper("abc:toto", "abc:\"toto\"");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_single_term_with_float() {
|
||||||
test_parse_query_to_ast_helper("abc:1.1", "abc:\"1.1\"");
|
test_parse_query_to_ast_helper("abc:1.1", "abc:\"1.1\"");
|
||||||
test_parse_query_to_ast_helper("+abc:toto", "+(abc:\"toto\")");
|
}
|
||||||
test_parse_query_to_ast_helper("(+abc:toto -titi)", "(+(abc:\"toto\") -(\"titi\"))");
|
|
||||||
test_parse_query_to_ast_helper("-abc:toto", "-(abc:\"toto\")");
|
#[test]
|
||||||
test_parse_query_to_ast_helper("abc:a b", "(abc:\"a\" \"b\")");
|
fn test_must_clause() {
|
||||||
|
test_parse_query_to_ast_helper("(+a +b)", "(+\"a\" +\"b\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_test_query_plus_a_b_plus_d() {
|
||||||
|
test_parse_query_to_ast_helper("+(a b) +d", "(+(*\"a\" *\"b\") +\"d\")");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_test_query_other() {
|
||||||
|
test_parse_query_to_ast_helper("(+a +b) d", "(*(+\"a\" +\"b\") *\"d\")");
|
||||||
|
test_parse_query_to_ast_helper("+abc:toto", "abc:\"toto\"");
|
||||||
|
test_parse_query_to_ast_helper("(+abc:toto -titi)", "(+abc:\"toto\" -\"titi\")");
|
||||||
|
test_parse_query_to_ast_helper("-abc:toto", "(-abc:\"toto\")");
|
||||||
|
test_parse_query_to_ast_helper("abc:a b", "(*abc:\"a\" *\"b\")");
|
||||||
test_parse_query_to_ast_helper("abc:\"a b\"", "abc:\"a b\"");
|
test_parse_query_to_ast_helper("abc:\"a b\"", "abc:\"a b\"");
|
||||||
test_parse_query_to_ast_helper("foo:[1 TO 5]", "foo:[\"1\" TO \"5\"]");
|
test_parse_query_to_ast_helper("foo:[1 TO 5]", "foo:[\"1\" TO \"5\"]");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_query_with_range() {
|
||||||
test_parse_query_to_ast_helper("[1 TO 5]", "[\"1\" TO \"5\"]");
|
test_parse_query_to_ast_helper("[1 TO 5]", "[\"1\" TO \"5\"]");
|
||||||
test_parse_query_to_ast_helper("foo:{a TO z}", "foo:{\"a\" TO \"z\"}");
|
test_parse_query_to_ast_helper("foo:{a TO z}", "foo:{\"a\" TO \"z\"}");
|
||||||
test_parse_query_to_ast_helper("foo:[1 TO toto}", "foo:[\"1\" TO \"toto\"}");
|
test_parse_query_to_ast_helper("foo:[1 TO toto}", "foo:[\"1\" TO \"toto\"}");
|
||||||
|
|||||||
@@ -85,15 +85,14 @@ impl UserInputBound {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub enum UserInputAST {
|
pub enum UserInputAST {
|
||||||
Clause(Vec<UserInputAST>),
|
Clause(Vec<(Option<Occur>, UserInputAST)>),
|
||||||
Unary(Occur, Box<UserInputAST>),
|
|
||||||
Leaf(Box<UserInputLeaf>),
|
Leaf(Box<UserInputLeaf>),
|
||||||
Boost(Box<UserInputAST>, f32),
|
Boost(Box<UserInputAST>, f32),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl UserInputAST {
|
impl UserInputAST {
|
||||||
pub fn unary(self, occur: Occur) -> UserInputAST {
|
pub fn unary(self, occur: Occur) -> UserInputAST {
|
||||||
UserInputAST::Unary(occur, Box::new(self))
|
UserInputAST::Clause(vec![(Some(occur), self)])
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compose(occur: Occur, asts: Vec<UserInputAST>) -> UserInputAST {
|
fn compose(occur: Occur, asts: Vec<UserInputAST>) -> UserInputAST {
|
||||||
@@ -104,7 +103,7 @@ impl UserInputAST {
|
|||||||
} else {
|
} else {
|
||||||
UserInputAST::Clause(
|
UserInputAST::Clause(
|
||||||
asts.into_iter()
|
asts.into_iter()
|
||||||
.map(|ast: UserInputAST| ast.unary(occur))
|
.map(|ast: UserInputAST| (Some(occur), ast))
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -135,25 +134,36 @@ impl From<UserInputLeaf> for UserInputAST {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_occur_ast(
|
||||||
|
occur_opt: Option<Occur>,
|
||||||
|
ast: &UserInputAST,
|
||||||
|
formatter: &mut fmt::Formatter,
|
||||||
|
) -> fmt::Result {
|
||||||
|
if let Some(occur) = occur_opt {
|
||||||
|
write!(formatter, "{}{:?}", occur, ast)?;
|
||||||
|
} else {
|
||||||
|
write!(formatter, "*{:?}", ast)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
impl fmt::Debug for UserInputAST {
|
impl fmt::Debug for UserInputAST {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
fn fmt(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
match *self {
|
match *self {
|
||||||
UserInputAST::Clause(ref subqueries) => {
|
UserInputAST::Clause(ref subqueries) => {
|
||||||
if subqueries.is_empty() {
|
if subqueries.is_empty() {
|
||||||
write!(formatter, "<emptyclause>")?;
|
write!(formatter, "<emptyclause>")?;
|
||||||
} else {
|
} else {
|
||||||
write!(formatter, "(")?;
|
write!(formatter, "(")?;
|
||||||
write!(formatter, "{:?}", &subqueries[0])?;
|
print_occur_ast(subqueries[0].0, &subqueries[0].1, formatter)?;
|
||||||
for subquery in &subqueries[1..] {
|
for subquery in &subqueries[1..] {
|
||||||
write!(formatter, " {:?}", subquery)?;
|
write!(formatter, " ")?;
|
||||||
|
print_occur_ast(subquery.0, &subquery.1, formatter)?;
|
||||||
}
|
}
|
||||||
write!(formatter, ")")?;
|
write!(formatter, ")")?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
UserInputAST::Unary(ref occur, ref subquery) => {
|
|
||||||
write!(formatter, "{}({:?})", occur, subquery)
|
|
||||||
}
|
|
||||||
UserInputAST::Leaf(ref subquery) => write!(formatter, "{:?}", subquery),
|
UserInputAST::Leaf(ref subquery) => write!(formatter, "{:?}", subquery),
|
||||||
UserInputAST::Boost(ref leaf, boost) => write!(formatter, "({:?})^{}", leaf, boost),
|
UserInputAST::Boost(ref leaf, boost) => write!(formatter, "({:?})^{}", leaf, boost),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -450,7 +450,6 @@ mod tests {
|
|||||||
use crate::Index;
|
use crate::Index;
|
||||||
use crate::IndexWriter;
|
use crate::IndexWriter;
|
||||||
use crate::Score;
|
use crate::Score;
|
||||||
use itertools::Itertools;
|
|
||||||
|
|
||||||
fn make_index() -> Index {
|
fn make_index() -> Index {
|
||||||
let mut schema_builder = Schema::builder();
|
let mut schema_builder = Schema::builder();
|
||||||
@@ -524,8 +523,8 @@ mod tests {
|
|||||||
|
|
||||||
// precondition for the test to be meaningful: we did get documents
|
// precondition for the test to be meaningful: we did get documents
|
||||||
// with the same score
|
// with the same score
|
||||||
assert!(page_1.iter().map(|result| result.0).all_equal());
|
assert!(page_1.iter().all(|result| result.0 == page_1[0].0));
|
||||||
assert!(page_2.iter().map(|result| result.0).all_equal());
|
assert!(page_2.iter().all(|result| result.0 == page_2[0].0));
|
||||||
|
|
||||||
// sanity check since we're relying on make_index()
|
// sanity check since we're relying on make_index()
|
||||||
assert_eq!(page_1.len(), 2);
|
assert_eq!(page_1.len(), 2);
|
||||||
|
|||||||
@@ -18,6 +18,19 @@ pub use byteorder::LittleEndian as Endianness;
|
|||||||
/// We do not allow segments with more than
|
/// We do not allow segments with more than
|
||||||
pub const MAX_DOC_LIMIT: u32 = 1 << 31;
|
pub const MAX_DOC_LIMIT: u32 = 1 << 31;
|
||||||
|
|
||||||
|
pub fn minmax<I, T>(mut vals: I) -> Option<(T, T)>
|
||||||
|
where
|
||||||
|
I: Iterator<Item = T>,
|
||||||
|
T: Copy + Ord,
|
||||||
|
{
|
||||||
|
if let Some(first_el) = vals.next() {
|
||||||
|
return Some(vals.fold((first_el, first_el), |(min_val, max_val), el| {
|
||||||
|
(min_val.min(el), max_val.max(el))
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Computes the number of bits that will be used for bitpacking.
|
/// Computes the number of bits that will be used for bitpacking.
|
||||||
///
|
///
|
||||||
/// In general the target is the minimum number of bits
|
/// In general the target is the minimum number of bits
|
||||||
@@ -134,6 +147,7 @@ pub fn u64_to_f64(val: u64) -> f64 {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) mod test {
|
pub(crate) mod test {
|
||||||
|
|
||||||
|
pub use super::minmax;
|
||||||
pub use super::serialize::test::fixed_size_test;
|
pub use super::serialize::test::fixed_size_test;
|
||||||
use super::{compute_num_bits, f64_to_u64, i64_to_u64, u64_to_f64, u64_to_i64};
|
use super::{compute_num_bits, f64_to_u64, i64_to_u64, u64_to_f64, u64_to_i64};
|
||||||
use std::f64;
|
use std::f64;
|
||||||
@@ -199,4 +213,21 @@ pub(crate) mod test {
|
|||||||
assert!(((super::MAX_DOC_LIMIT - 1) as i32) >= 0);
|
assert!(((super::MAX_DOC_LIMIT - 1) as i32) >= 0);
|
||||||
assert!((super::MAX_DOC_LIMIT as i32) < 0);
|
assert!((super::MAX_DOC_LIMIT as i32) < 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_minmax_empty() {
|
||||||
|
let vals: Vec<u32> = vec![];
|
||||||
|
assert_eq!(minmax(vals.into_iter()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_minmax_one() {
|
||||||
|
assert_eq!(minmax(vec![1].into_iter()), Some((1, 1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_minmax_two() {
|
||||||
|
assert_eq!(minmax(vec![1, 2].into_iter()), Some((1, 2)));
|
||||||
|
assert_eq!(minmax(vec![2, 1].into_iter()), Some((1, 2)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ impl Index {
|
|||||||
TantivyError::LockFailure(
|
TantivyError::LockFailure(
|
||||||
err,
|
err,
|
||||||
Some(
|
Some(
|
||||||
"Failed to acquire index lock. If you are using \
|
"Failed to acquire index lock. If you are using\
|
||||||
a regular directory, this means there is already an \
|
a regular directory, this means there is already an \
|
||||||
`IndexWriter` working on this `Directory`, in this process \
|
`IndexWriter` working on this `Directory`, in this process \
|
||||||
or in a different process."
|
or in a different process."
|
||||||
@@ -334,7 +334,8 @@ impl Index {
|
|||||||
.collect())
|
.collect())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn segment(&self, segment_meta: SegmentMeta) -> Segment {
|
#[doc(hidden)]
|
||||||
|
pub fn segment(&self, segment_meta: SegmentMeta) -> Segment {
|
||||||
Segment::for_index(self.clone(), segment_meta)
|
Segment::for_index(self.clone(), segment_meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,13 +347,6 @@ impl Index {
|
|||||||
self.segment(segment_meta)
|
self.segment(segment_meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new segment.
|
|
||||||
pub(crate) fn new_segment_unpersisted(&self) -> Segment {
|
|
||||||
let meta = self
|
|
||||||
.inventory
|
|
||||||
.new_segment_meta(SegmentId::generate_random(), 0);
|
|
||||||
Segment::new_volatile(meta, self.schema())
|
|
||||||
}
|
|
||||||
/// Return a reference to the index directory.
|
/// Return a reference to the index directory.
|
||||||
pub fn directory(&self) -> &ManagedDirectory {
|
pub fn directory(&self) -> &ManagedDirectory {
|
||||||
&self.directory
|
&self.directory
|
||||||
@@ -438,7 +432,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_should_wipe_off_existing() {
|
fn create_should_wipeoff_existing() {
|
||||||
let directory = RAMDirectory::create();
|
let directory = RAMDirectory::create();
|
||||||
assert!(Index::create(directory.clone(), throw_away_schema()).is_ok());
|
assert!(Index::create(directory.clone(), throw_away_schema()).is_ok());
|
||||||
assert!(Index::exists(&directory));
|
assert!(Index::exists(&directory));
|
||||||
@@ -475,7 +469,7 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
test_index_on_commit_reload_policy_aux(field, index.clone(), &index, &reader);
|
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "mmap")]
|
#[cfg(feature = "mmap")]
|
||||||
@@ -499,7 +493,7 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
test_index_on_commit_reload_policy_aux(field, index.clone(), &index, &reader);
|
test_index_on_commit_reload_policy_aux(field, &index, &reader);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -541,16 +535,12 @@ mod tests {
|
|||||||
.try_into()
|
.try_into()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(reader.searcher().num_docs(), 0);
|
assert_eq!(reader.searcher().num_docs(), 0);
|
||||||
test_index_on_commit_reload_policy_aux(field, read_index, &write_index, &reader);
|
test_index_on_commit_reload_policy_aux(field, &write_index, &reader);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_index_on_commit_reload_policy_aux(
|
fn test_index_on_commit_reload_policy_aux(field: Field, index: &Index, reader: &IndexReader) {
|
||||||
field: Field,
|
let mut reader_index = reader.index();
|
||||||
mut reader_index: Index,
|
|
||||||
index: &Index,
|
|
||||||
reader: &IndexReader,
|
|
||||||
) {
|
|
||||||
let (sender, receiver) = crossbeam::channel::unbounded();
|
let (sender, receiver) = crossbeam::channel::unbounded();
|
||||||
let _watch_handle = reader_index.directory_mut().watch(Box::new(move || {
|
let _watch_handle = reader_index.directory_mut().watch(Box::new(move || {
|
||||||
let _ = sender.send(());
|
let _ = sender.send(());
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use crate::schema::Schema;
|
|||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
use census::{Inventory, TrackedObject};
|
use census::{Inventory, TrackedObject};
|
||||||
use serde;
|
use serde;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json;
|
use serde_json;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|||||||
@@ -3,59 +3,19 @@ use crate::core::Index;
|
|||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::directory::error::{OpenReadError, OpenWriteError};
|
use crate::directory::error::{OpenReadError, OpenWriteError};
|
||||||
use crate::directory::{Directory, ManagedDirectory, RAMDirectory};
|
use crate::directory::Directory;
|
||||||
use crate::directory::{ReadOnlySource, WritePtr};
|
use crate::directory::{ReadOnlySource, WritePtr};
|
||||||
use crate::indexer::segment_serializer::SegmentSerializer;
|
use crate::indexer::segment_serializer::SegmentSerializer;
|
||||||
use crate::schema::Schema;
|
use crate::schema::Schema;
|
||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::ops::{Deref, DerefMut};
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub(crate) enum SegmentDirectory {
|
|
||||||
Persisted(ManagedDirectory),
|
|
||||||
Volatile(RAMDirectory),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SegmentDirectory {
|
|
||||||
pub fn new_volatile() -> SegmentDirectory {
|
|
||||||
SegmentDirectory::Volatile(RAMDirectory::default())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ManagedDirectory> for SegmentDirectory {
|
|
||||||
fn from(directory: ManagedDirectory) -> Self {
|
|
||||||
SegmentDirectory::Persisted(directory)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Deref for SegmentDirectory {
|
|
||||||
type Target = dyn Directory;
|
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
match self {
|
|
||||||
SegmentDirectory::Volatile(dir) => dir,
|
|
||||||
SegmentDirectory::Persisted(dir) => dir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DerefMut for SegmentDirectory {
|
|
||||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
||||||
match self {
|
|
||||||
SegmentDirectory::Volatile(dir) => dir,
|
|
||||||
SegmentDirectory::Persisted(dir) => dir,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A segment is a piece of the index.
|
/// A segment is a piece of the index.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Segment {
|
pub struct Segment {
|
||||||
schema: Schema,
|
index: Index,
|
||||||
meta: SegmentMeta,
|
meta: SegmentMeta,
|
||||||
directory: SegmentDirectory,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for Segment {
|
impl fmt::Debug for Segment {
|
||||||
@@ -65,55 +25,19 @@ impl fmt::Debug for Segment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Segment {
|
impl Segment {
|
||||||
/// Returns our index's schema.
|
|
||||||
// TODO return a ref.
|
|
||||||
pub fn schema(&self) -> Schema {
|
|
||||||
self.schema.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_persisted(
|
|
||||||
meta: SegmentMeta,
|
|
||||||
directory: ManagedDirectory,
|
|
||||||
schema: Schema,
|
|
||||||
) -> Segment {
|
|
||||||
Segment {
|
|
||||||
meta,
|
|
||||||
schema,
|
|
||||||
directory: SegmentDirectory::from(directory),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new segment that embeds its own `RAMDirectory`.
|
|
||||||
///
|
|
||||||
/// That segment is entirely dissociated from the index directory.
|
|
||||||
/// It will be persisted by a background thread in charge of IO.
|
|
||||||
pub fn new_volatile(meta: SegmentMeta, schema: Schema) -> Segment {
|
|
||||||
Segment {
|
|
||||||
schema,
|
|
||||||
meta,
|
|
||||||
directory: SegmentDirectory::new_volatile(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Creates a new segment given an `Index` and a `SegmentId`
|
/// Creates a new segment given an `Index` and a `SegmentId`
|
||||||
pub(crate) fn for_index(index: Index, meta: SegmentMeta) -> Segment {
|
pub(crate) fn for_index(index: Index, meta: SegmentMeta) -> Segment {
|
||||||
Segment {
|
Segment { index, meta }
|
||||||
directory: SegmentDirectory::Persisted(index.directory().clone()),
|
|
||||||
schema: index.schema(),
|
|
||||||
meta,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(&mut self, mut dest_directory: ManagedDirectory) -> crate::Result<()> {
|
/// Returns the index the segment belongs to.
|
||||||
if let SegmentDirectory::Persisted(_) = self.directory {
|
pub fn index(&self) -> &Index {
|
||||||
// this segment is already persisted.
|
&self.index
|
||||||
return Ok(());
|
}
|
||||||
}
|
|
||||||
if let SegmentDirectory::Volatile(ram_directory) = &self.directory {
|
/// Returns our index's schema.
|
||||||
ram_directory.persist(&mut dest_directory)?;
|
pub fn schema(&self) -> Schema {
|
||||||
}
|
self.index.schema()
|
||||||
self.directory = SegmentDirectory::Persisted(dest_directory);
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the segment meta-information
|
/// Returns the segment meta-information
|
||||||
@@ -127,8 +51,7 @@ impl Segment {
|
|||||||
/// as we finalize a fresh new segment.
|
/// as we finalize a fresh new segment.
|
||||||
pub(crate) fn with_max_doc(self, max_doc: u32) -> Segment {
|
pub(crate) fn with_max_doc(self, max_doc: u32) -> Segment {
|
||||||
Segment {
|
Segment {
|
||||||
directory: self.directory,
|
index: self.index,
|
||||||
schema: self.schema,
|
|
||||||
meta: self.meta.with_max_doc(max_doc),
|
meta: self.meta.with_max_doc(max_doc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,8 +59,7 @@ impl Segment {
|
|||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn with_delete_meta(self, num_deleted_docs: u32, opstamp: Opstamp) -> Segment {
|
pub fn with_delete_meta(self, num_deleted_docs: u32, opstamp: Opstamp) -> Segment {
|
||||||
Segment {
|
Segment {
|
||||||
directory: self.directory,
|
index: self.index,
|
||||||
schema: self.schema,
|
|
||||||
meta: self.meta.with_delete_meta(num_deleted_docs, opstamp),
|
meta: self.meta.with_delete_meta(num_deleted_docs, opstamp),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,14 +80,14 @@ impl Segment {
|
|||||||
/// Open one of the component file for a *regular* read.
|
/// Open one of the component file for a *regular* read.
|
||||||
pub fn open_read(&self, component: SegmentComponent) -> Result<ReadOnlySource, OpenReadError> {
|
pub fn open_read(&self, component: SegmentComponent) -> Result<ReadOnlySource, OpenReadError> {
|
||||||
let path = self.relative_path(component);
|
let path = self.relative_path(component);
|
||||||
let source = self.directory.open_read(&path)?;
|
let source = self.index.directory().open_read(&path)?;
|
||||||
Ok(source)
|
Ok(source)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Open one of the component file for *regular* write.
|
/// Open one of the component file for *regular* write.
|
||||||
pub fn open_write(&mut self, component: SegmentComponent) -> Result<WritePtr, OpenWriteError> {
|
pub fn open_write(&mut self, component: SegmentComponent) -> Result<WritePtr, OpenWriteError> {
|
||||||
let path = self.relative_path(component);
|
let path = self.relative_path(component);
|
||||||
let write = self.directory.open_write(&path)?;
|
let write = self.index.directory_mut().open_write(&path)?;
|
||||||
Ok(write)
|
Ok(write)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ use uuid::Uuid;
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -56,68 +56,6 @@ pub struct SegmentReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentReader {
|
impl SegmentReader {
|
||||||
/// Open a new segment for reading.
|
|
||||||
pub fn open(segment: &Segment) -> crate::Result<SegmentReader> {
|
|
||||||
let termdict_source = segment.open_read(SegmentComponent::TERMS)?;
|
|
||||||
let termdict_composite = CompositeFile::open(&termdict_source)?;
|
|
||||||
|
|
||||||
let store_source = segment.open_read(SegmentComponent::STORE)?;
|
|
||||||
|
|
||||||
fail_point!("SegmentReader::open#middle");
|
|
||||||
|
|
||||||
let postings_source = segment.open_read(SegmentComponent::POSTINGS)?;
|
|
||||||
let postings_composite = CompositeFile::open(&postings_source)?;
|
|
||||||
|
|
||||||
let positions_composite = {
|
|
||||||
if let Ok(source) = segment.open_read(SegmentComponent::POSITIONS) {
|
|
||||||
CompositeFile::open(&source)?
|
|
||||||
} else {
|
|
||||||
CompositeFile::empty()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let positions_idx_composite = {
|
|
||||||
if let Ok(source) = segment.open_read(SegmentComponent::POSITIONSSKIP) {
|
|
||||||
CompositeFile::open(&source)?
|
|
||||||
} else {
|
|
||||||
CompositeFile::empty()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let schema = segment.schema();
|
|
||||||
|
|
||||||
let fast_fields_data = segment.open_read(SegmentComponent::FASTFIELDS)?;
|
|
||||||
let fast_fields_composite = CompositeFile::open(&fast_fields_data)?;
|
|
||||||
let fast_field_readers =
|
|
||||||
Arc::new(FastFieldReaders::load_all(&schema, &fast_fields_composite)?);
|
|
||||||
|
|
||||||
let fieldnorms_data = segment.open_read(SegmentComponent::FIELDNORMS)?;
|
|
||||||
let fieldnorms_composite = CompositeFile::open(&fieldnorms_data)?;
|
|
||||||
|
|
||||||
let delete_bitset_opt = if segment.meta().has_deletes() {
|
|
||||||
let delete_data = segment.open_read(SegmentComponent::DELETE)?;
|
|
||||||
Some(DeleteBitSet::open(delete_data))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(SegmentReader {
|
|
||||||
inv_idx_reader_cache: Arc::new(RwLock::new(HashMap::new())),
|
|
||||||
max_doc: segment.meta().max_doc(),
|
|
||||||
num_docs: segment.meta().num_docs(),
|
|
||||||
termdict_composite,
|
|
||||||
postings_composite,
|
|
||||||
fast_fields_readers: fast_field_readers,
|
|
||||||
fieldnorms_composite,
|
|
||||||
segment_id: segment.id(),
|
|
||||||
store_source,
|
|
||||||
delete_bitset_opt,
|
|
||||||
positions_composite,
|
|
||||||
positions_idx_composite,
|
|
||||||
schema,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the highest document id ever attributed in
|
/// Returns the highest document id ever attributed in
|
||||||
/// this segment + 1.
|
/// this segment + 1.
|
||||||
/// Today, `tantivy` does not handle deletes, so it happens
|
/// Today, `tantivy` does not handle deletes, so it happens
|
||||||
@@ -204,68 +142,68 @@ impl SegmentReader {
|
|||||||
pub fn get_store_reader(&self) -> StoreReader {
|
pub fn get_store_reader(&self) -> StoreReader {
|
||||||
StoreReader::from_source(self.store_source.clone())
|
StoreReader::from_source(self.store_source.clone())
|
||||||
}
|
}
|
||||||
//
|
|
||||||
// /// Open a new segment for reading.
|
/// Open a new segment for reading.
|
||||||
// pub fn open(segment: &Segment) -> crate::Result<SegmentReader> {
|
pub fn open(segment: &Segment) -> crate::Result<SegmentReader> {
|
||||||
// let termdict_source = segment.open_read(SegmentComponent::TERMS)?;
|
let termdict_source = segment.open_read(SegmentComponent::TERMS)?;
|
||||||
// let termdict_composite = CompositeFile::open(&termdict_source)?;
|
let termdict_composite = CompositeFile::open(&termdict_source)?;
|
||||||
//
|
|
||||||
// let store_source = segment.open_read(SegmentComponent::STORE)?;
|
let store_source = segment.open_read(SegmentComponent::STORE)?;
|
||||||
//
|
|
||||||
// fail_point!("SegmentReader::open#middle");
|
fail_point!("SegmentReader::open#middle");
|
||||||
//
|
|
||||||
// let postings_source = segment.open_read(SegmentComponent::POSTINGS)?;
|
let postings_source = segment.open_read(SegmentComponent::POSTINGS)?;
|
||||||
// let postings_composite = CompositeFile::open(&postings_source)?;
|
let postings_composite = CompositeFile::open(&postings_source)?;
|
||||||
//
|
|
||||||
// let positions_composite = {
|
let positions_composite = {
|
||||||
// if let Ok(source) = segment.open_read(SegmentComponent::POSITIONS) {
|
if let Ok(source) = segment.open_read(SegmentComponent::POSITIONS) {
|
||||||
// CompositeFile::open(&source)?
|
CompositeFile::open(&source)?
|
||||||
// } else {
|
} else {
|
||||||
// CompositeFile::empty()
|
CompositeFile::empty()
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
//
|
|
||||||
// let positions_idx_composite = {
|
let positions_idx_composite = {
|
||||||
// if let Ok(source) = segment.open_read(SegmentComponent::POSITIONSSKIP) {
|
if let Ok(source) = segment.open_read(SegmentComponent::POSITIONSSKIP) {
|
||||||
// CompositeFile::open(&source)?
|
CompositeFile::open(&source)?
|
||||||
// } else {
|
} else {
|
||||||
// CompositeFile::empty()
|
CompositeFile::empty()
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
//
|
|
||||||
// let schema = segment.schema();
|
let schema = segment.schema();
|
||||||
//
|
|
||||||
// let fast_fields_data = segment.open_read(SegmentComponent::FASTFIELDS)?;
|
let fast_fields_data = segment.open_read(SegmentComponent::FASTFIELDS)?;
|
||||||
// let fast_fields_composite = CompositeFile::open(&fast_fields_data)?;
|
let fast_fields_composite = CompositeFile::open(&fast_fields_data)?;
|
||||||
// let fast_field_readers =
|
let fast_field_readers =
|
||||||
// Arc::new(FastFieldReaders::load_all(&schema, &fast_fields_composite)?);
|
Arc::new(FastFieldReaders::load_all(&schema, &fast_fields_composite)?);
|
||||||
//
|
|
||||||
// let fieldnorms_data = segment.open_read(SegmentComponent::FIELDNORMS)?;
|
let fieldnorms_data = segment.open_read(SegmentComponent::FIELDNORMS)?;
|
||||||
// let fieldnorms_composite = CompositeFile::open(&fieldnorms_data)?;
|
let fieldnorms_composite = CompositeFile::open(&fieldnorms_data)?;
|
||||||
//
|
|
||||||
// let delete_bitset_opt = if segment.meta().has_deletes() {
|
let delete_bitset_opt = if segment.meta().has_deletes() {
|
||||||
// let delete_data = segment.open_read(SegmentComponent::DELETE)?;
|
let delete_data = segment.open_read(SegmentComponent::DELETE)?;
|
||||||
// Some(DeleteBitSet::open(delete_data))
|
Some(DeleteBitSet::open(delete_data))
|
||||||
// } else {
|
} else {
|
||||||
// None
|
None
|
||||||
// };
|
};
|
||||||
//
|
|
||||||
// Ok(SegmentReader {
|
Ok(SegmentReader {
|
||||||
// inv_idx_reader_cache: Arc::new(RwLock::new(HashMap::new())),
|
inv_idx_reader_cache: Arc::new(RwLock::new(HashMap::new())),
|
||||||
// max_doc: segment.meta().max_doc(),
|
max_doc: segment.meta().max_doc(),
|
||||||
// num_docs: segment.meta().num_docs(),
|
num_docs: segment.meta().num_docs(),
|
||||||
// termdict_composite,
|
termdict_composite,
|
||||||
// postings_composite,
|
postings_composite,
|
||||||
// fast_fields_readers: fast_field_readers,
|
fast_fields_readers: fast_field_readers,
|
||||||
// fieldnorms_composite,
|
fieldnorms_composite,
|
||||||
// segment_id: segment.id(),
|
segment_id: segment.id(),
|
||||||
// store_source,
|
store_source,
|
||||||
// delete_bitset_opt,
|
delete_bitset_opt,
|
||||||
// positions_composite,
|
positions_composite,
|
||||||
// positions_idx_composite,
|
positions_idx_composite,
|
||||||
// schema,
|
schema,
|
||||||
// })
|
})
|
||||||
// }
|
}
|
||||||
|
|
||||||
/// Returns a field reader associated to the field given in argument.
|
/// Returns a field reader associated to the field given in argument.
|
||||||
/// If the field was not present in the index during indexing time,
|
/// If the field was not present in the index during indexing time,
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ use crate::directory::WatchHandle;
|
|||||||
use crate::directory::{TerminatingWrite, WritePtr};
|
use crate::directory::{TerminatingWrite, WritePtr};
|
||||||
use atomicwrites;
|
use atomicwrites;
|
||||||
use memmap::Mmap;
|
use memmap::Mmap;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::convert::From;
|
use std::convert::From;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ impl RAMDirectory {
|
|||||||
self.fs.read().unwrap().total_mem_usage()
|
self.fs.read().unwrap().total_mem_usage()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write a copy of all of the files saved in the RAMDirectory in the target `Directory`.
|
||||||
|
///
|
||||||
|
/// Files are all written using the `Directory::write` meaning, even if they were
|
||||||
|
/// written using the `atomic_write` api.
|
||||||
|
///
|
||||||
|
/// If an error is encounterred, files may be persisted partially.
|
||||||
pub fn persist(&self, dest: &mut dyn Directory) -> crate::Result<()> {
|
pub fn persist(&self, dest: &mut dyn Directory) -> crate::Result<()> {
|
||||||
let wlock = self.fs.write().unwrap();
|
let wlock = self.fs.write().unwrap();
|
||||||
for (path, source) in wlock.fs.iter() {
|
for (path, source) in wlock.fs.iter() {
|
||||||
@@ -214,3 +220,28 @@ impl Directory for RAMDirectory {
|
|||||||
Ok(self.fs.write().unwrap().watch(watch_callback))
|
Ok(self.fs.write().unwrap().watch(watch_callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::RAMDirectory;
|
||||||
|
use crate::Directory;
|
||||||
|
use std::io::Write;
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_persist() {
|
||||||
|
let msg_atomic: &'static [u8] = b"atomic is the way";
|
||||||
|
let msg_seq: &'static [u8] = b"sequential is the way";
|
||||||
|
let path_atomic: &'static Path = Path::new("atomic");
|
||||||
|
let path_seq: &'static Path = Path::new("seq");
|
||||||
|
let mut directory = RAMDirectory::create();
|
||||||
|
assert!(directory.atomic_write(path_atomic, msg_atomic).is_ok());
|
||||||
|
let mut wrt = directory.open_write(path_seq).unwrap();
|
||||||
|
assert!(wrt.write_all(msg_seq).is_ok());
|
||||||
|
assert!(wrt.flush().is_ok());
|
||||||
|
let mut directory_copy = RAMDirectory::create();
|
||||||
|
assert!(directory.persist(&mut directory_copy).is_ok());
|
||||||
|
assert_eq!(directory_copy.atomic_read(path_atomic).unwrap(), msg_atomic);
|
||||||
|
assert_eq!(directory_copy.atomic_read(path_seq).unwrap(), msg_seq);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ use crate::schema::{Document, Field};
|
|||||||
use crate::termdict::TermOrdinal;
|
use crate::termdict::TermOrdinal;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use fnv::FnvHashMap;
|
use fnv::FnvHashMap;
|
||||||
use itertools::Itertools;
|
|
||||||
use std::io;
|
use std::io;
|
||||||
|
|
||||||
/// Writer for multi-valued (as in, more than one value per document)
|
/// Writer for multi-valued (as in, more than one value per document)
|
||||||
@@ -151,8 +150,8 @@ impl MultiValueIntFastFieldWriter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
let val_min_max = self.vals.iter().cloned().minmax();
|
let val_min_max = crate::common::minmax(self.vals.iter().cloned());
|
||||||
let (val_min, val_max) = val_min_max.into_option().unwrap_or((0u64, 0u64));
|
let (val_min, val_max) = val_min_max.unwrap_or((0u64, 0u64));
|
||||||
value_serializer =
|
value_serializer =
|
||||||
serializer.new_u64_fast_field_with_idx(self.field, val_min, val_max, 1)?;
|
serializer.new_u64_fast_field_with_idx(self.field, val_min, val_max, 1)?;
|
||||||
for &val in &self.vals {
|
for &val in &self.vals {
|
||||||
|
|||||||
@@ -8,33 +8,30 @@ use crate::core::SegmentComponent;
|
|||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::core::SegmentReader;
|
use crate::core::SegmentReader;
|
||||||
|
use crate::directory::TerminatingWrite;
|
||||||
use crate::directory::{DirectoryLock, GarbageCollectionResult};
|
use crate::directory::{DirectoryLock, GarbageCollectionResult};
|
||||||
use crate::directory::{TerminatingWrite, WatchCallbackList};
|
|
||||||
use crate::docset::DocSet;
|
use crate::docset::DocSet;
|
||||||
use crate::error::TantivyError;
|
use crate::error::TantivyError;
|
||||||
use crate::fastfield::write_delete_bitset;
|
use crate::fastfield::write_delete_bitset;
|
||||||
use crate::indexer::delete_queue::{DeleteCursor, DeleteQueue};
|
use crate::indexer::delete_queue::{DeleteCursor, DeleteQueue};
|
||||||
use crate::indexer::doc_opstamp_mapping::DocToOpstampMapping;
|
use crate::indexer::doc_opstamp_mapping::DocToOpstampMapping;
|
||||||
use crate::indexer::operation::DeleteOperation;
|
use crate::indexer::operation::DeleteOperation;
|
||||||
use crate::indexer::segment_manager::SegmentRegisters;
|
|
||||||
use crate::indexer::segment_register::SegmentRegister;
|
|
||||||
use crate::indexer::stamper::Stamper;
|
use crate::indexer::stamper::Stamper;
|
||||||
use crate::indexer::MergePolicy;
|
use crate::indexer::MergePolicy;
|
||||||
use crate::indexer::SegmentEntry;
|
use crate::indexer::SegmentEntry;
|
||||||
use crate::indexer::SegmentWriter;
|
use crate::indexer::SegmentWriter;
|
||||||
use crate::reader::NRTReader;
|
|
||||||
use crate::schema::Document;
|
use crate::schema::Document;
|
||||||
use crate::schema::IndexRecordOption;
|
use crate::schema::IndexRecordOption;
|
||||||
use crate::schema::Term;
|
use crate::schema::Term;
|
||||||
use crate::tokenizer::TokenizerManager;
|
use crate::Opstamp;
|
||||||
use crate::{IndexReader, Opstamp};
|
|
||||||
use crossbeam::channel;
|
use crossbeam::channel;
|
||||||
use futures::executor::block_on;
|
use futures::executor::block_on;
|
||||||
use futures::future::Future;
|
use futures::future::Future;
|
||||||
use smallvec::{smallvec, SmallVec};
|
use smallvec::smallvec;
|
||||||
|
use smallvec::SmallVec;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::Arc;
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::thread::JoinHandle;
|
use std::thread::JoinHandle;
|
||||||
|
|
||||||
@@ -71,8 +68,6 @@ pub struct IndexWriter {
|
|||||||
// lifetime of the lock with that of the IndexWriter.
|
// lifetime of the lock with that of the IndexWriter.
|
||||||
_directory_lock: Option<DirectoryLock>,
|
_directory_lock: Option<DirectoryLock>,
|
||||||
|
|
||||||
segment_registers: Arc<RwLock<SegmentRegisters>>,
|
|
||||||
|
|
||||||
index: Index,
|
index: Index,
|
||||||
|
|
||||||
heap_size_in_bytes_per_thread: usize,
|
heap_size_in_bytes_per_thread: usize,
|
||||||
@@ -92,8 +87,6 @@ pub struct IndexWriter {
|
|||||||
|
|
||||||
stamper: Stamper,
|
stamper: Stamper,
|
||||||
committed_opstamp: Opstamp,
|
committed_opstamp: Opstamp,
|
||||||
|
|
||||||
on_commit: WatchCallbackList,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_deleted_bitset(
|
fn compute_deleted_bitset(
|
||||||
@@ -140,6 +133,7 @@ fn compute_deleted_bitset(
|
|||||||
/// For instance, there was no delete operation between the state of the `segment_entry` and
|
/// For instance, there was no delete operation between the state of the `segment_entry` and
|
||||||
/// the `target_opstamp`, `segment_entry` is not updated.
|
/// the `target_opstamp`, `segment_entry` is not updated.
|
||||||
pub(crate) fn advance_deletes(
|
pub(crate) fn advance_deletes(
|
||||||
|
mut segment: Segment,
|
||||||
segment_entry: &mut SegmentEntry,
|
segment_entry: &mut SegmentEntry,
|
||||||
target_opstamp: Opstamp,
|
target_opstamp: Opstamp,
|
||||||
) -> crate::Result<()> {
|
) -> crate::Result<()> {
|
||||||
@@ -148,38 +142,32 @@ pub(crate) fn advance_deletes(
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
let delete_bitset_opt = segment_entry.take_delete_bitset();
|
if segment_entry.delete_bitset().is_none() && segment_entry.delete_cursor().get().is_none() {
|
||||||
|
|
||||||
// We avoid directly advancing the `SegmentEntry` delete cursor, because
|
|
||||||
// we do not want to end up in an invalid state if the delete bitset
|
|
||||||
// serialization fails.
|
|
||||||
let mut delete_cursor = segment_entry.delete_cursor();
|
|
||||||
|
|
||||||
if delete_bitset_opt.is_none() && delete_cursor.get().is_none() {
|
|
||||||
// There has been no `DeleteOperation` between the segment status and `target_opstamp`.
|
// There has been no `DeleteOperation` between the segment status and `target_opstamp`.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
// We open our current serialized segment to compute the new deleted bitset.
|
|
||||||
let segment = segment_entry.segment().clone();
|
|
||||||
let segment_reader = SegmentReader::open(&segment)?;
|
let segment_reader = SegmentReader::open(&segment)?;
|
||||||
|
|
||||||
let max_doc = segment_reader.max_doc();
|
let max_doc = segment_reader.max_doc();
|
||||||
let mut delete_bitset: BitSet =
|
let mut delete_bitset: BitSet = match segment_entry.delete_bitset() {
|
||||||
delete_bitset_opt.unwrap_or_else(|| BitSet::with_max_value(max_doc));
|
Some(previous_delete_bitset) => (*previous_delete_bitset).clone(),
|
||||||
|
None => BitSet::with_max_value(max_doc),
|
||||||
|
};
|
||||||
|
|
||||||
let num_deleted_docs_before = segment.meta().num_deleted_docs();
|
let num_deleted_docs_before = segment.meta().num_deleted_docs();
|
||||||
|
|
||||||
compute_deleted_bitset(
|
compute_deleted_bitset(
|
||||||
&mut delete_bitset,
|
&mut delete_bitset,
|
||||||
&segment_reader,
|
&segment_reader,
|
||||||
&mut delete_cursor,
|
segment_entry.delete_cursor(),
|
||||||
&DocToOpstampMapping::None,
|
&DocToOpstampMapping::None,
|
||||||
target_opstamp,
|
target_opstamp,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO optimize... We are simply manipulating bitsets here.
|
// TODO optimize
|
||||||
// We should be able to compute the union much faster.
|
// It should be possible to do something smarter by manipulation bitsets directly
|
||||||
|
// to compute this union.
|
||||||
if let Some(seg_delete_bitset) = segment_reader.delete_bitset() {
|
if let Some(seg_delete_bitset) = segment_reader.delete_bitset() {
|
||||||
for doc in 0u32..max_doc {
|
for doc in 0u32..max_doc {
|
||||||
if seg_delete_bitset.is_deleted(doc) {
|
if seg_delete_bitset.is_deleted(doc) {
|
||||||
@@ -188,23 +176,16 @@ pub(crate) fn advance_deletes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_deleted_docs = delete_bitset.len() as u32;
|
let num_deleted_docs: u32 = delete_bitset.len() as u32;
|
||||||
|
|
||||||
if num_deleted_docs > num_deleted_docs_before {
|
if num_deleted_docs > num_deleted_docs_before {
|
||||||
// We need to write a new delete file.
|
// There are new deletes. We need to write a new delete file.
|
||||||
let mut delete_file = segment
|
segment = segment.with_delete_meta(num_deleted_docs as u32, target_opstamp);
|
||||||
.with_delete_meta(num_deleted_docs, target_opstamp)
|
let mut delete_file = segment.open_write(SegmentComponent::DELETE)?;
|
||||||
.open_write(SegmentComponent::DELETE)?;
|
|
||||||
write_delete_bitset(&delete_bitset, max_doc, &mut delete_file)?;
|
write_delete_bitset(&delete_bitset, max_doc, &mut delete_file)?;
|
||||||
delete_file.terminate()?;
|
delete_file.terminate()?;
|
||||||
segment_entry.reset_delete_meta(num_deleted_docs as u32, target_opstamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Regardless of whether we did end up having to write a new file or not
|
segment_entry.set_meta(segment.meta().clone());
|
||||||
// we advance the `delete_cursor`. This is an optimisation. We want to ensure we do not
|
|
||||||
// check that a given deleted term does not match any of our docs more than once.
|
|
||||||
segment_entry.set_delete_cursor(delete_cursor);
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -213,12 +194,11 @@ fn index_documents(
|
|||||||
segment: Segment,
|
segment: Segment,
|
||||||
grouped_document_iterator: &mut dyn Iterator<Item = OperationGroup>,
|
grouped_document_iterator: &mut dyn Iterator<Item = OperationGroup>,
|
||||||
segment_updater: &mut SegmentUpdater,
|
segment_updater: &mut SegmentUpdater,
|
||||||
tokenizers: &TokenizerManager,
|
|
||||||
mut delete_cursor: DeleteCursor,
|
mut delete_cursor: DeleteCursor,
|
||||||
) -> crate::Result<bool> {
|
) -> crate::Result<bool> {
|
||||||
let schema = segment.schema();
|
let schema = segment.schema();
|
||||||
let mut segment_writer =
|
|
||||||
SegmentWriter::for_segment(memory_budget, segment.clone(), &schema, tokenizers)?;
|
let mut segment_writer = SegmentWriter::for_segment(memory_budget, segment.clone(), &schema)?;
|
||||||
for document_group in grouped_document_iterator {
|
for document_group in grouped_document_iterator {
|
||||||
for doc in document_group {
|
for doc in document_group {
|
||||||
segment_writer.add_document(doc, &schema)?;
|
segment_writer.add_document(doc, &schema)?;
|
||||||
@@ -256,7 +236,11 @@ fn index_documents(
|
|||||||
last_docstamp,
|
last_docstamp,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let segment_entry = SegmentEntry::new(segment_with_max_doc, delete_cursor, delete_bitset_opt);
|
let segment_entry = SegmentEntry::new(
|
||||||
|
segment_with_max_doc.meta().clone(),
|
||||||
|
delete_cursor,
|
||||||
|
delete_bitset_opt,
|
||||||
|
);
|
||||||
block_on(segment_updater.schedule_add_segment(segment_entry))?;
|
block_on(segment_updater.schedule_add_segment(segment_entry))?;
|
||||||
Ok(true)
|
Ok(true)
|
||||||
}
|
}
|
||||||
@@ -328,24 +312,16 @@ impl IndexWriter {
|
|||||||
|
|
||||||
let delete_queue = DeleteQueue::new();
|
let delete_queue = DeleteQueue::new();
|
||||||
|
|
||||||
let meta = index.load_metas()?;
|
let current_opstamp = index.load_metas()?.opstamp;
|
||||||
|
|
||||||
let stamper = Stamper::new(meta.opstamp);
|
let stamper = Stamper::new(current_opstamp);
|
||||||
|
|
||||||
let commited_segments = SegmentRegister::new(
|
|
||||||
index.directory(),
|
|
||||||
&index.schema(),
|
|
||||||
meta.segments,
|
|
||||||
&delete_queue.cursor(),
|
|
||||||
);
|
|
||||||
let segment_registers = Arc::new(RwLock::new(SegmentRegisters::new(commited_segments)));
|
|
||||||
|
|
||||||
let segment_updater =
|
let segment_updater =
|
||||||
SegmentUpdater::create(segment_registers.clone(), index.clone(), stamper.clone())?;
|
SegmentUpdater::create(index.clone(), stamper.clone(), &delete_queue.cursor())?;
|
||||||
|
|
||||||
let mut index_writer = IndexWriter {
|
let mut index_writer = IndexWriter {
|
||||||
_directory_lock: Some(directory_lock),
|
_directory_lock: Some(directory_lock),
|
||||||
segment_registers,
|
|
||||||
heap_size_in_bytes_per_thread,
|
heap_size_in_bytes_per_thread,
|
||||||
index: index.clone(),
|
index: index.clone(),
|
||||||
|
|
||||||
@@ -359,12 +335,10 @@ impl IndexWriter {
|
|||||||
|
|
||||||
delete_queue,
|
delete_queue,
|
||||||
|
|
||||||
committed_opstamp: meta.opstamp,
|
committed_opstamp: current_opstamp,
|
||||||
stamper,
|
stamper,
|
||||||
|
|
||||||
worker_id: 0,
|
worker_id: 0,
|
||||||
|
|
||||||
on_commit: Default::default(),
|
|
||||||
};
|
};
|
||||||
index_writer.start_workers()?;
|
index_writer.start_workers()?;
|
||||||
Ok(index_writer)
|
Ok(index_writer)
|
||||||
@@ -404,6 +378,13 @@ impl IndexWriter {
|
|||||||
result
|
result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub fn add_segment(&self, segment_meta: SegmentMeta) -> crate::Result<()> {
|
||||||
|
let delete_cursor = self.delete_queue.cursor();
|
||||||
|
let segment_entry = SegmentEntry::new(segment_meta, delete_cursor, None);
|
||||||
|
block_on(self.segment_updater.schedule_add_segment(segment_entry))
|
||||||
|
}
|
||||||
|
|
||||||
/// Creates a new segment.
|
/// Creates a new segment.
|
||||||
///
|
///
|
||||||
/// This method is useful only for users trying to do complex
|
/// This method is useful only for users trying to do complex
|
||||||
@@ -452,13 +433,12 @@ impl IndexWriter {
|
|||||||
// was dropped.
|
// was dropped.
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
let segment = index.new_segment_unpersisted();
|
let segment = index.new_segment();
|
||||||
index_documents(
|
index_documents(
|
||||||
mem_budget,
|
mem_budget,
|
||||||
segment,
|
segment,
|
||||||
&mut document_iterator,
|
&mut document_iterator,
|
||||||
&mut segment_updater,
|
&mut segment_updater,
|
||||||
index.tokenizers(),
|
|
||||||
delete_cursor.clone(),
|
delete_cursor.clone(),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
@@ -485,21 +465,6 @@ impl IndexWriter {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO move me
|
|
||||||
pub(crate) fn trigger_commit(&self) -> impl Future<Output = ()> {
|
|
||||||
self.on_commit.broadcast()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reader(&self, num_searchers: usize) -> crate::Result<IndexReader> {
|
|
||||||
let nrt_reader = NRTReader::create(
|
|
||||||
num_searchers,
|
|
||||||
self.index.clone(),
|
|
||||||
self.segment_registers.clone(),
|
|
||||||
&self.on_commit,
|
|
||||||
)?;
|
|
||||||
Ok(IndexReader::NRT(nrt_reader))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Detects and removes the files that are not used by the index anymore.
|
/// Detects and removes the files that are not used by the index anymore.
|
||||||
pub fn garbage_collect_files(
|
pub fn garbage_collect_files(
|
||||||
&self,
|
&self,
|
||||||
@@ -643,7 +608,7 @@ impl IndexWriter {
|
|||||||
/// It is also possible to add a payload to the `commit`
|
/// It is also possible to add a payload to the `commit`
|
||||||
/// using this API.
|
/// using this API.
|
||||||
/// See [`PreparedCommit::set_payload()`](PreparedCommit.html)
|
/// See [`PreparedCommit::set_payload()`](PreparedCommit.html)
|
||||||
pub fn prepare_commit(&mut self, soft_commit: bool) -> crate::Result<PreparedCommit> {
|
pub fn prepare_commit(&mut self) -> crate::Result<PreparedCommit> {
|
||||||
// Here, because we join all of the worker threads,
|
// Here, because we join all of the worker threads,
|
||||||
// all of the segment update for this commit have been
|
// all of the segment update for this commit have been
|
||||||
// sent.
|
// sent.
|
||||||
@@ -671,7 +636,7 @@ impl IndexWriter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let commit_opstamp = self.stamper.stamp();
|
let commit_opstamp = self.stamper.stamp();
|
||||||
let prepared_commit = PreparedCommit::new(self, commit_opstamp, soft_commit);
|
let prepared_commit = PreparedCommit::new(self, commit_opstamp);
|
||||||
info!("Prepared commit {}", commit_opstamp);
|
info!("Prepared commit {}", commit_opstamp);
|
||||||
Ok(prepared_commit)
|
Ok(prepared_commit)
|
||||||
}
|
}
|
||||||
@@ -691,11 +656,7 @@ impl IndexWriter {
|
|||||||
/// that made it in the commit.
|
/// that made it in the commit.
|
||||||
///
|
///
|
||||||
pub fn commit(&mut self) -> crate::Result<Opstamp> {
|
pub fn commit(&mut self) -> crate::Result<Opstamp> {
|
||||||
self.prepare_commit(false)?.commit()
|
self.prepare_commit()?.commit()
|
||||||
}
|
|
||||||
|
|
||||||
pub fn soft_commit(&mut self) -> crate::Result<Opstamp> {
|
|
||||||
self.prepare_commit(true)?.commit()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn segment_updater(&self) -> &SegmentUpdater {
|
pub(crate) fn segment_updater(&self) -> &SegmentUpdater {
|
||||||
@@ -820,6 +781,7 @@ impl Drop for IndexWriter {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
||||||
use super::super::operation::UserOperation;
|
use super::super::operation::UserOperation;
|
||||||
use crate::collector::TopDocs;
|
use crate::collector::TopDocs;
|
||||||
use crate::directory::error::LockError;
|
use crate::directory::error::LockError;
|
||||||
@@ -846,6 +808,46 @@ mod tests {
|
|||||||
assert_eq!(batch_opstamp1, 2u64);
|
assert_eq!(batch_opstamp1, 2u64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_no_need_to_rewrite_delete_file_if_no_new_deletes() {
|
||||||
|
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 mut index_writer = index.writer_with_num_threads(1, 3_000_000).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();
|
||||||
|
let searcher = reader.searcher();
|
||||||
|
assert_eq!(searcher.segment_readers().len(), 1);
|
||||||
|
assert_eq!(searcher.segment_reader(0u32).num_deleted_docs(), 0);
|
||||||
|
|
||||||
|
index_writer.delete_term(Term::from_field_text(text_field, "hello1"));
|
||||||
|
assert!(index_writer.commit().is_ok());
|
||||||
|
|
||||||
|
assert!(reader.reload().is_ok());
|
||||||
|
let searcher = reader.searcher();
|
||||||
|
assert_eq!(searcher.segment_readers().len(), 1);
|
||||||
|
assert_eq!(searcher.segment_reader(0u32).num_deleted_docs(), 1);
|
||||||
|
|
||||||
|
let previous_delete_opstamp = index.load_metas().unwrap().segments[0].delete_opstamp();
|
||||||
|
|
||||||
|
// All docs containing hello1 have been already removed.
|
||||||
|
// We should not update the delete meta.
|
||||||
|
index_writer.delete_term(Term::from_field_text(text_field, "hello1"));
|
||||||
|
assert!(index_writer.commit().is_ok());
|
||||||
|
|
||||||
|
assert!(reader.reload().is_ok());
|
||||||
|
let searcher = reader.searcher();
|
||||||
|
assert_eq!(searcher.segment_readers().len(), 1);
|
||||||
|
assert_eq!(searcher.segment_reader(0u32).num_deleted_docs(), 1);
|
||||||
|
|
||||||
|
let after_delete_opstamp = index.load_metas().unwrap().segments[0].delete_opstamp();
|
||||||
|
assert_eq!(after_delete_opstamp, previous_delete_opstamp);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_ordered_batched_operations() {
|
fn test_ordered_batched_operations() {
|
||||||
// * one delete for `doc!(field=>"a")`
|
// * one delete for `doc!(field=>"a")`
|
||||||
@@ -1052,8 +1054,7 @@ mod tests {
|
|||||||
index_writer.add_document(doc!(text_field => "a"));
|
index_writer.add_document(doc!(text_field => "a"));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut prepared_commit =
|
let mut prepared_commit = index_writer.prepare_commit().expect("commit failed");
|
||||||
index_writer.prepare_commit(false).expect("commit failed");
|
|
||||||
prepared_commit.set_payload("first commit");
|
prepared_commit.set_payload("first commit");
|
||||||
prepared_commit.commit().expect("commit failed");
|
prepared_commit.commit().expect("commit failed");
|
||||||
}
|
}
|
||||||
@@ -1086,8 +1087,7 @@ mod tests {
|
|||||||
index_writer.add_document(doc!(text_field => "a"));
|
index_writer.add_document(doc!(text_field => "a"));
|
||||||
}
|
}
|
||||||
{
|
{
|
||||||
let mut prepared_commit =
|
let mut prepared_commit = index_writer.prepare_commit().expect("commit failed");
|
||||||
index_writer.prepare_commit(false).expect("commit failed");
|
|
||||||
prepared_commit.set_payload("first commit");
|
prepared_commit.set_payload("first commit");
|
||||||
prepared_commit.abort().expect("commit failed");
|
prepared_commit.abort().expect("commit failed");
|
||||||
}
|
}
|
||||||
@@ -1262,43 +1262,7 @@ mod tests {
|
|||||||
let index = Index::create_in_ram(schema_builder.build());
|
let index = Index::create_in_ram(schema_builder.build());
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
index_writer.add_document(doc!(idfield=>"myid"));
|
index_writer.add_document(doc!(idfield=>"myid"));
|
||||||
assert!(index_writer.commit().is_ok());
|
let commit = index_writer.commit();
|
||||||
}
|
assert!(commit.is_ok());
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_index_writer_reader() {
|
|
||||||
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_with_num_threads(1, 3_000_000).unwrap();
|
|
||||||
index_writer.add_document(doc!(idfield=>"myid"));
|
|
||||||
assert!(index_writer.commit().is_ok());
|
|
||||||
let reader = index_writer.reader(2).unwrap();
|
|
||||||
let searcher = reader.searcher();
|
|
||||||
assert_eq!(searcher.num_docs(), 1u64);
|
|
||||||
index_writer.add_document(doc!(idfield=>"myid"));
|
|
||||||
assert!(index_writer.commit().is_ok());
|
|
||||||
assert_eq!(reader.searcher().num_docs(), 2u64);
|
|
||||||
assert_eq!(searcher.num_docs(), 1u64);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_index_writer_reader_soft_commit() {
|
|
||||||
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_with_num_threads(1, 3_000_000).unwrap();
|
|
||||||
index_writer.add_document(doc!(idfield=>"myid"));
|
|
||||||
assert!(index_writer.soft_commit().is_ok());
|
|
||||||
let nrt_reader = index_writer.reader(2).unwrap();
|
|
||||||
let normal_reader = index.reader_builder().try_into().unwrap();
|
|
||||||
assert_eq!(nrt_reader.searcher().num_docs(), 1u64);
|
|
||||||
assert_eq!(normal_reader.searcher().num_docs(), 0u64);
|
|
||||||
assert!(index_writer.commit().is_ok());
|
|
||||||
assert!(normal_reader.reload().is_ok());
|
|
||||||
assert_eq!(nrt_reader.searcher().num_docs(), 1u64);
|
|
||||||
assert_eq!(normal_reader.searcher().num_docs(), 1u64);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ use crate::store::StoreWriter;
|
|||||||
use crate::termdict::TermMerger;
|
use crate::termdict::TermMerger;
|
||||||
use crate::termdict::TermOrdinal;
|
use crate::termdict::TermOrdinal;
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use itertools::Itertools;
|
|
||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
@@ -70,11 +69,11 @@ fn compute_min_max_val(
|
|||||||
Some(delete_bitset) => {
|
Some(delete_bitset) => {
|
||||||
// some deleted documents,
|
// some deleted documents,
|
||||||
// we need to recompute the max / min
|
// we need to recompute the max / min
|
||||||
(0..max_doc)
|
crate::common::minmax(
|
||||||
.filter(|doc_id| delete_bitset.is_alive(*doc_id))
|
(0..max_doc)
|
||||||
.map(|doc_id| u64_reader.get(doc_id))
|
.filter(|doc_id| delete_bitset.is_alive(*doc_id))
|
||||||
.minmax()
|
.map(|doc_id| u64_reader.get(doc_id)),
|
||||||
.into_option()
|
)
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
// no deleted documents,
|
// no deleted documents,
|
||||||
|
|||||||
@@ -23,7 +23,6 @@ pub use self::merge_policy::{MergeCandidate, MergePolicy, NoMergePolicy};
|
|||||||
pub use self::prepared_commit::PreparedCommit;
|
pub use self::prepared_commit::PreparedCommit;
|
||||||
pub use self::segment_entry::SegmentEntry;
|
pub use self::segment_entry::SegmentEntry;
|
||||||
pub use self::segment_manager::SegmentManager;
|
pub use self::segment_manager::SegmentManager;
|
||||||
pub(crate) use self::segment_manager::SegmentRegisters;
|
|
||||||
pub use self::segment_serializer::SegmentSerializer;
|
pub use self::segment_serializer::SegmentSerializer;
|
||||||
pub use self::segment_writer::SegmentWriter;
|
pub use self::segment_writer::SegmentWriter;
|
||||||
|
|
||||||
|
|||||||
@@ -7,20 +7,14 @@ pub struct PreparedCommit<'a> {
|
|||||||
index_writer: &'a mut IndexWriter,
|
index_writer: &'a mut IndexWriter,
|
||||||
payload: Option<String>,
|
payload: Option<String>,
|
||||||
opstamp: Opstamp,
|
opstamp: Opstamp,
|
||||||
soft_commit: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> PreparedCommit<'a> {
|
impl<'a> PreparedCommit<'a> {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(index_writer: &'a mut IndexWriter, opstamp: Opstamp) -> PreparedCommit<'_> {
|
||||||
index_writer: &'a mut IndexWriter,
|
|
||||||
opstamp: Opstamp,
|
|
||||||
soft_commit: bool,
|
|
||||||
) -> PreparedCommit {
|
|
||||||
PreparedCommit {
|
PreparedCommit {
|
||||||
index_writer,
|
index_writer,
|
||||||
payload: None,
|
payload: None,
|
||||||
opstamp,
|
opstamp,
|
||||||
soft_commit,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,12 +32,11 @@ impl<'a> PreparedCommit<'a> {
|
|||||||
|
|
||||||
pub fn commit(self) -> crate::Result<Opstamp> {
|
pub fn commit(self) -> crate::Result<Opstamp> {
|
||||||
info!("committing {}", self.opstamp);
|
info!("committing {}", self.opstamp);
|
||||||
block_on(self.index_writer.segment_updater().schedule_commit(
|
let _ = block_on(
|
||||||
self.opstamp,
|
self.index_writer
|
||||||
self.payload,
|
.segment_updater()
|
||||||
self.soft_commit,
|
.schedule_commit(self.opstamp, self.payload),
|
||||||
))?;
|
);
|
||||||
block_on(self.index_writer.trigger_commit());
|
|
||||||
Ok(self.opstamp)
|
Ok(self.opstamp)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use crate::common::BitSet;
|
use crate::common::BitSet;
|
||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::directory::ManagedDirectory;
|
|
||||||
use crate::indexer::delete_queue::DeleteCursor;
|
use crate::indexer::delete_queue::DeleteCursor;
|
||||||
use crate::{Opstamp, Segment};
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
/// A segment entry describes the state of
|
/// A segment entry describes the state of
|
||||||
@@ -21,81 +19,55 @@ use std::fmt;
|
|||||||
/// in the .del file or in the `delete_bitset`.
|
/// in the .del file or in the `delete_bitset`.
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct SegmentEntry {
|
pub struct SegmentEntry {
|
||||||
segment: Segment,
|
meta: SegmentMeta,
|
||||||
delete_bitset: Option<BitSet>,
|
delete_bitset: Option<BitSet>,
|
||||||
delete_cursor: DeleteCursor,
|
delete_cursor: DeleteCursor,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentEntry {
|
impl SegmentEntry {
|
||||||
/// Create a new `SegmentEntry`
|
/// Create a new `SegmentEntry`
|
||||||
pub(crate) fn new(
|
pub fn new(
|
||||||
segment: Segment,
|
segment_meta: SegmentMeta,
|
||||||
delete_cursor: DeleteCursor,
|
delete_cursor: DeleteCursor,
|
||||||
delete_bitset: Option<BitSet>,
|
delete_bitset: Option<BitSet>,
|
||||||
) -> SegmentEntry {
|
) -> SegmentEntry {
|
||||||
SegmentEntry {
|
SegmentEntry {
|
||||||
segment,
|
meta: segment_meta,
|
||||||
delete_bitset,
|
delete_bitset,
|
||||||
delete_cursor,
|
delete_cursor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn persist(&mut self, dest_directory: ManagedDirectory) -> crate::Result<()> {
|
/// Return a reference to the segment entry deleted bitset.
|
||||||
// TODO take in account delete bitset?
|
|
||||||
self.segment.persist(dest_directory)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn segment(&self) -> &Segment {
|
|
||||||
&self.segment
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `Takes` (as in Option::take) the delete bitset of
|
|
||||||
/// a segment entry.
|
|
||||||
///
|
///
|
||||||
/// `DocId` in this bitset are flagged as deleted.
|
/// `DocId` in this bitset are flagged as deleted.
|
||||||
pub fn take_delete_bitset(&mut self) -> Option<BitSet> {
|
pub fn delete_bitset(&self) -> Option<&BitSet> {
|
||||||
self.delete_bitset.take()
|
self.delete_bitset.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Reset the delete informmationo in this segment.
|
/// Set the `SegmentMeta` for this segment.
|
||||||
///
|
pub fn set_meta(&mut self, segment_meta: SegmentMeta) {
|
||||||
/// The `SegmentEntry` segment's `SegmentMeta` gets updated, and
|
self.meta = segment_meta;
|
||||||
/// any delete bitset is drop and set to None.
|
|
||||||
pub fn reset_delete_meta(&mut self, num_deleted_docs: u32, target_opstamp: Opstamp) {
|
|
||||||
self.segment = self
|
|
||||||
.segment
|
|
||||||
.clone()
|
|
||||||
.with_delete_meta(num_deleted_docs, target_opstamp);
|
|
||||||
self.delete_bitset = None;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_delete_cursor(&mut self, delete_cursor: DeleteCursor) {
|
|
||||||
self.delete_cursor = delete_cursor;
|
|
||||||
}
|
|
||||||
/// Return a reference to the segment_entry's delete cursor
|
/// Return a reference to the segment_entry's delete cursor
|
||||||
pub fn delete_cursor(&mut self) -> DeleteCursor {
|
pub fn delete_cursor(&mut self) -> &mut DeleteCursor {
|
||||||
self.delete_cursor.clone()
|
&mut self.delete_cursor
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the segment id.
|
/// Returns the segment id.
|
||||||
pub fn segment_id(&self) -> SegmentId {
|
pub fn segment_id(&self) -> SegmentId {
|
||||||
self.segment.id()
|
self.meta.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Accessor to the `SegmentMeta`
|
/// Accessor to the `SegmentMeta`
|
||||||
pub fn meta(&self) -> &SegmentMeta {
|
pub fn meta(&self) -> &SegmentMeta {
|
||||||
self.segment.meta()
|
&self.meta
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Debug for SegmentEntry {
|
impl fmt::Debug for SegmentEntry {
|
||||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
let num_deletes = self.delete_bitset.as_ref().map(|bitset| bitset.len());
|
write!(formatter, "SegmentEntry({:?})", self.meta)
|
||||||
write!(
|
|
||||||
formatter,
|
|
||||||
"SegmentEntry(seg={:?}, ndel={:?})",
|
|
||||||
self.segment, num_deletes
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
use super::segment_register::SegmentRegister;
|
use super::segment_register::SegmentRegister;
|
||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
|
use crate::error::TantivyError;
|
||||||
|
use crate::indexer::delete_queue::DeleteCursor;
|
||||||
use crate::indexer::SegmentEntry;
|
use crate::indexer::SegmentEntry;
|
||||||
use crate::Segment;
|
|
||||||
use std::collections::hash_set::HashSet;
|
use std::collections::hash_set::HashSet;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
|
use std::sync::RwLock;
|
||||||
use std::sync::{RwLockReadGuard, RwLockWriteGuard};
|
use std::sync::{RwLockReadGuard, RwLockWriteGuard};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub(crate) struct SegmentRegisters {
|
struct SegmentRegisters {
|
||||||
uncommitted: SegmentRegister,
|
uncommitted: SegmentRegister,
|
||||||
committed: SegmentRegister,
|
committed: SegmentRegister,
|
||||||
}
|
}
|
||||||
@@ -20,17 +22,6 @@ pub(crate) enum SegmentsStatus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentRegisters {
|
impl SegmentRegisters {
|
||||||
pub fn new(committed: SegmentRegister) -> SegmentRegisters {
|
|
||||||
SegmentRegisters {
|
|
||||||
uncommitted: Default::default(),
|
|
||||||
committed,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn committed_segment(&self) -> Vec<Segment> {
|
|
||||||
self.committed.segments()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if all the segments are committed or uncommited.
|
/// Check if all the segments are committed or uncommited.
|
||||||
///
|
///
|
||||||
/// If some segment is missing or segments are in a different state (this should not happen
|
/// If some segment is missing or segments are in a different state (this should not happen
|
||||||
@@ -53,7 +44,18 @@ impl SegmentRegisters {
|
|||||||
/// changes (merges especially)
|
/// changes (merges especially)
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SegmentManager {
|
pub struct SegmentManager {
|
||||||
registers: Arc<RwLock<SegmentRegisters>>,
|
registers: RwLock<SegmentRegisters>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for SegmentManager {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
let lock = self.read();
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{{ uncommitted: {:?}, committed: {:?} }}",
|
||||||
|
lock.uncommitted, lock.committed
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_mergeable_segments(
|
pub fn get_mergeable_segments(
|
||||||
@@ -72,8 +74,16 @@ pub fn get_mergeable_segments(
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SegmentManager {
|
impl SegmentManager {
|
||||||
pub(crate) fn new(registers: Arc<RwLock<SegmentRegisters>>) -> SegmentManager {
|
pub fn from_segments(
|
||||||
SegmentManager { registers }
|
segment_metas: Vec<SegmentMeta>,
|
||||||
|
delete_cursor: &DeleteCursor,
|
||||||
|
) -> SegmentManager {
|
||||||
|
SegmentManager {
|
||||||
|
registers: RwLock::new(SegmentRegisters {
|
||||||
|
uncommitted: SegmentRegister::default(),
|
||||||
|
committed: SegmentRegister::new(segment_metas, delete_cursor),
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all of the segment entries (committed or uncommitted)
|
/// Returns all of the segment entries (committed or uncommitted)
|
||||||
@@ -155,7 +165,7 @@ impl SegmentManager {
|
|||||||
let error_msg = "Merge operation sent for segments that are not \
|
let error_msg = "Merge operation sent for segments that are not \
|
||||||
all uncommited or commited."
|
all uncommited or commited."
|
||||||
.to_string();
|
.to_string();
|
||||||
return Err(crate::Error::InvalidArgument(error_msg));
|
return Err(TantivyError::InvalidArgument(error_msg));
|
||||||
}
|
}
|
||||||
Ok(segment_entries)
|
Ok(segment_entries)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
use crate::core::SegmentId;
|
use crate::core::SegmentId;
|
||||||
use crate::core::SegmentMeta;
|
use crate::core::SegmentMeta;
|
||||||
use crate::directory::ManagedDirectory;
|
|
||||||
use crate::indexer::delete_queue::DeleteCursor;
|
use crate::indexer::delete_queue::DeleteCursor;
|
||||||
use crate::indexer::segment_entry::SegmentEntry;
|
use crate::indexer::segment_entry::SegmentEntry;
|
||||||
use crate::schema::Schema;
|
|
||||||
use crate::Segment;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
@@ -49,13 +46,6 @@ impl SegmentRegister {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn segments(&self) -> Vec<Segment> {
|
|
||||||
self.segment_states
|
|
||||||
.values()
|
|
||||||
.map(|segment_entry| segment_entry.segment().clone())
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn segment_entries(&self) -> Vec<SegmentEntry> {
|
pub fn segment_entries(&self) -> Vec<SegmentEntry> {
|
||||||
self.segment_states.values().cloned().collect()
|
self.segment_states.values().cloned().collect()
|
||||||
}
|
}
|
||||||
@@ -89,17 +79,11 @@ impl SegmentRegister {
|
|||||||
self.segment_states.get(segment_id).cloned()
|
self.segment_states.get(segment_id).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn new(
|
pub fn new(segment_metas: Vec<SegmentMeta>, delete_cursor: &DeleteCursor) -> SegmentRegister {
|
||||||
directory: &ManagedDirectory,
|
|
||||||
schema: &Schema,
|
|
||||||
segment_metas: Vec<SegmentMeta>,
|
|
||||||
delete_cursor: &DeleteCursor,
|
|
||||||
) -> SegmentRegister {
|
|
||||||
let mut segment_states = HashMap::new();
|
let mut segment_states = HashMap::new();
|
||||||
for meta in segment_metas {
|
for segment_meta in segment_metas {
|
||||||
let segment_id = meta.id();
|
let segment_id = segment_meta.id();
|
||||||
let segment = Segment::new_persisted(meta, directory.clone(), schema.clone());
|
let segment_entry = SegmentEntry::new(segment_meta, delete_cursor.clone(), None);
|
||||||
let segment_entry = SegmentEntry::new(segment, delete_cursor.clone(), None);
|
|
||||||
segment_states.insert(segment_id, segment_entry);
|
segment_states.insert(segment_id, segment_entry);
|
||||||
}
|
}
|
||||||
SegmentRegister { segment_states }
|
SegmentRegister { segment_states }
|
||||||
@@ -111,7 +95,6 @@ mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::core::{SegmentId, SegmentMetaInventory};
|
use crate::core::{SegmentId, SegmentMetaInventory};
|
||||||
use crate::indexer::delete_queue::*;
|
use crate::indexer::delete_queue::*;
|
||||||
use crate::schema::Schema;
|
|
||||||
|
|
||||||
fn segment_ids(segment_register: &SegmentRegister) -> Vec<SegmentId> {
|
fn segment_ids(segment_register: &SegmentRegister) -> Vec<SegmentId> {
|
||||||
segment_register
|
segment_register
|
||||||
@@ -125,7 +108,6 @@ mod tests {
|
|||||||
fn test_segment_register() {
|
fn test_segment_register() {
|
||||||
let inventory = SegmentMetaInventory::default();
|
let inventory = SegmentMetaInventory::default();
|
||||||
let delete_queue = DeleteQueue::new();
|
let delete_queue = DeleteQueue::new();
|
||||||
let schema = Schema::builder().build();
|
|
||||||
|
|
||||||
let mut segment_register = SegmentRegister::default();
|
let mut segment_register = SegmentRegister::default();
|
||||||
let segment_id_a = SegmentId::generate_random();
|
let segment_id_a = SegmentId::generate_random();
|
||||||
@@ -133,24 +115,21 @@ mod tests {
|
|||||||
let segment_id_merged = SegmentId::generate_random();
|
let segment_id_merged = SegmentId::generate_random();
|
||||||
|
|
||||||
{
|
{
|
||||||
let meta = inventory.new_segment_meta(segment_id_a, 0u32);
|
let segment_meta = inventory.new_segment_meta(segment_id_a, 0u32);
|
||||||
let segment = Segment::new_volatile(meta, schema.clone());
|
let segment_entry = SegmentEntry::new(segment_meta, delete_queue.cursor(), None);
|
||||||
let segment_entry = SegmentEntry::new(segment, delete_queue.cursor(), None);
|
|
||||||
segment_register.add_segment_entry(segment_entry);
|
segment_register.add_segment_entry(segment_entry);
|
||||||
}
|
}
|
||||||
assert_eq!(segment_ids(&segment_register), vec![segment_id_a]);
|
assert_eq!(segment_ids(&segment_register), vec![segment_id_a]);
|
||||||
{
|
{
|
||||||
let segment_meta = inventory.new_segment_meta(segment_id_b, 0u32);
|
let segment_meta = inventory.new_segment_meta(segment_id_b, 0u32);
|
||||||
let segment = Segment::new_volatile(segment_meta, schema.clone());
|
let segment_entry = SegmentEntry::new(segment_meta, delete_queue.cursor(), None);
|
||||||
let segment_entry = SegmentEntry::new(segment, delete_queue.cursor(), None);
|
|
||||||
segment_register.add_segment_entry(segment_entry);
|
segment_register.add_segment_entry(segment_entry);
|
||||||
}
|
}
|
||||||
segment_register.remove_segment(&segment_id_a);
|
segment_register.remove_segment(&segment_id_a);
|
||||||
segment_register.remove_segment(&segment_id_b);
|
segment_register.remove_segment(&segment_id_b);
|
||||||
{
|
{
|
||||||
let segment_meta_merged = inventory.new_segment_meta(segment_id_merged, 0u32);
|
let segment_meta_merged = inventory.new_segment_meta(segment_id_merged, 0u32);
|
||||||
let segment = Segment::new_volatile(segment_meta_merged, schema);
|
let segment_entry = SegmentEntry::new(segment_meta_merged, delete_queue.cursor(), None);
|
||||||
let segment_entry = SegmentEntry::new(segment, delete_queue.cursor(), None);
|
|
||||||
segment_register.add_segment_entry(segment_entry);
|
segment_register.add_segment_entry(segment_entry);
|
||||||
}
|
}
|
||||||
assert_eq!(segment_ids(&segment_register), vec![segment_id_merged]);
|
assert_eq!(segment_ids(&segment_register), vec![segment_id_merged]);
|
||||||
|
|||||||
@@ -7,10 +7,11 @@ use crate::core::SegmentMeta;
|
|||||||
use crate::core::SerializableSegment;
|
use crate::core::SerializableSegment;
|
||||||
use crate::core::META_FILEPATH;
|
use crate::core::META_FILEPATH;
|
||||||
use crate::directory::{Directory, DirectoryClone, GarbageCollectionResult};
|
use crate::directory::{Directory, DirectoryClone, GarbageCollectionResult};
|
||||||
|
use crate::indexer::delete_queue::DeleteCursor;
|
||||||
use crate::indexer::index_writer::advance_deletes;
|
use crate::indexer::index_writer::advance_deletes;
|
||||||
use crate::indexer::merge_operation::MergeOperationInventory;
|
use crate::indexer::merge_operation::MergeOperationInventory;
|
||||||
use crate::indexer::merger::IndexMerger;
|
use crate::indexer::merger::IndexMerger;
|
||||||
use crate::indexer::segment_manager::{SegmentRegisters, SegmentsStatus};
|
use crate::indexer::segment_manager::SegmentsStatus;
|
||||||
use crate::indexer::stamper::Stamper;
|
use crate::indexer::stamper::Stamper;
|
||||||
use crate::indexer::SegmentEntry;
|
use crate::indexer::SegmentEntry;
|
||||||
use crate::indexer::SegmentSerializer;
|
use crate::indexer::SegmentSerializer;
|
||||||
@@ -116,7 +117,8 @@ fn merge(
|
|||||||
|
|
||||||
// First we apply all of the delet to the merged segment, up to the target opstamp.
|
// First we apply all of the delet to the merged segment, up to the target opstamp.
|
||||||
for segment_entry in &mut segment_entries {
|
for segment_entry in &mut segment_entries {
|
||||||
advance_deletes(segment_entry, target_opstamp)?;
|
let segment = index.segment(segment_entry.meta().clone());
|
||||||
|
advance_deletes(segment, segment_entry, target_opstamp)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let delete_cursor = segment_entries[0].delete_cursor().clone();
|
let delete_cursor = segment_entries[0].delete_cursor().clone();
|
||||||
@@ -132,13 +134,11 @@ fn merge(
|
|||||||
// ... we just serialize this index merger in our new segment to merge the two segments.
|
// ... we just serialize this index merger in our new segment to merge the two segments.
|
||||||
let segment_serializer = SegmentSerializer::for_segment(&mut merged_segment)?;
|
let segment_serializer = SegmentSerializer::for_segment(&mut merged_segment)?;
|
||||||
|
|
||||||
let max_doc = merger.write(segment_serializer)?;
|
let num_docs = merger.write(segment_serializer)?;
|
||||||
|
|
||||||
Ok(SegmentEntry::new(
|
let segment_meta = index.new_segment_meta(merged_segment.id(), num_docs);
|
||||||
merged_segment.with_max_doc(max_doc),
|
|
||||||
delete_cursor,
|
Ok(SegmentEntry::new(segment_meta, delete_cursor, None))
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct InnerSegmentUpdater {
|
pub(crate) struct InnerSegmentUpdater {
|
||||||
@@ -162,11 +162,12 @@ pub(crate) struct InnerSegmentUpdater {
|
|||||||
|
|
||||||
impl SegmentUpdater {
|
impl SegmentUpdater {
|
||||||
pub fn create(
|
pub fn create(
|
||||||
segment_registers: Arc<RwLock<SegmentRegisters>>,
|
|
||||||
index: Index,
|
index: Index,
|
||||||
stamper: Stamper,
|
stamper: Stamper,
|
||||||
|
delete_cursor: &DeleteCursor,
|
||||||
) -> crate::Result<SegmentUpdater> {
|
) -> crate::Result<SegmentUpdater> {
|
||||||
let segment_manager = SegmentManager::new(segment_registers);
|
let segments = index.searchable_segment_metas()?;
|
||||||
|
let segment_manager = SegmentManager::from_segments(segments, delete_cursor);
|
||||||
let pool = ThreadPoolBuilder::new()
|
let pool = ThreadPoolBuilder::new()
|
||||||
.name_prefix("segment_updater")
|
.name_prefix("segment_updater")
|
||||||
.pool_size(1)
|
.pool_size(1)
|
||||||
@@ -233,7 +234,6 @@ impl SegmentUpdater {
|
|||||||
&self,
|
&self,
|
||||||
segment_entry: SegmentEntry,
|
segment_entry: SegmentEntry,
|
||||||
) -> impl Future<Output = crate::Result<()>> {
|
) -> impl Future<Output = crate::Result<()>> {
|
||||||
// TODO temporary: serializing the segment at this point.
|
|
||||||
let segment_updater = self.clone();
|
let segment_updater = self.clone();
|
||||||
self.schedule_future(async move {
|
self.schedule_future(async move {
|
||||||
segment_updater.segment_manager.add_segment(segment_entry);
|
segment_updater.segment_manager.add_segment(segment_entry);
|
||||||
@@ -262,7 +262,8 @@ impl SegmentUpdater {
|
|||||||
fn purge_deletes(&self, target_opstamp: Opstamp) -> crate::Result<Vec<SegmentEntry>> {
|
fn purge_deletes(&self, target_opstamp: Opstamp) -> crate::Result<Vec<SegmentEntry>> {
|
||||||
let mut segment_entries = self.segment_manager.segment_entries();
|
let mut segment_entries = self.segment_manager.segment_entries();
|
||||||
for segment_entry in &mut segment_entries {
|
for segment_entry in &mut segment_entries {
|
||||||
advance_deletes(segment_entry, target_opstamp)?;
|
let segment = self.index.segment(segment_entry.meta().clone());
|
||||||
|
advance_deletes(segment, segment_entry, target_opstamp)?;
|
||||||
}
|
}
|
||||||
Ok(segment_entries)
|
Ok(segment_entries)
|
||||||
}
|
}
|
||||||
@@ -330,21 +331,12 @@ impl SegmentUpdater {
|
|||||||
&self,
|
&self,
|
||||||
opstamp: Opstamp,
|
opstamp: Opstamp,
|
||||||
payload: Option<String>,
|
payload: Option<String>,
|
||||||
soft_commit: bool,
|
|
||||||
) -> impl Future<Output = crate::Result<()>> {
|
) -> impl Future<Output = crate::Result<()>> {
|
||||||
let segment_updater: SegmentUpdater = self.clone();
|
let segment_updater: SegmentUpdater = self.clone();
|
||||||
let directory = self.index.directory().clone();
|
|
||||||
self.schedule_future(async move {
|
self.schedule_future(async move {
|
||||||
let mut segment_entries = segment_updater.purge_deletes(opstamp)?;
|
let segment_entries = segment_updater.purge_deletes(opstamp)?;
|
||||||
if !soft_commit {
|
|
||||||
for segment_entry in &mut segment_entries {
|
|
||||||
segment_entry.persist(directory.clone())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
segment_updater.segment_manager.commit(segment_entries);
|
segment_updater.segment_manager.commit(segment_entries);
|
||||||
if !soft_commit {
|
segment_updater.save_metas(opstamp, payload)?;
|
||||||
segment_updater.save_metas(opstamp, payload)?;
|
|
||||||
}
|
|
||||||
let _ = garbage_collect_files(segment_updater.clone()).await;
|
let _ = garbage_collect_files(segment_updater.clone()).await;
|
||||||
segment_updater.consider_merge_options().await;
|
segment_updater.consider_merge_options().await;
|
||||||
Ok(())
|
Ok(())
|
||||||
@@ -482,14 +474,17 @@ impl SegmentUpdater {
|
|||||||
let end_merge_future = self.schedule_future(async move {
|
let end_merge_future = self.schedule_future(async move {
|
||||||
info!("End merge {:?}", after_merge_segment_entry.meta());
|
info!("End merge {:?}", after_merge_segment_entry.meta());
|
||||||
{
|
{
|
||||||
let mut delete_cursor = after_merge_segment_entry.delete_cursor();
|
let mut delete_cursor = after_merge_segment_entry.delete_cursor().clone();
|
||||||
if let Some(delete_operation) = delete_cursor.get() {
|
if let Some(delete_operation) = delete_cursor.get() {
|
||||||
let committed_opstamp = segment_updater.load_metas().opstamp;
|
let committed_opstamp = segment_updater.load_metas().opstamp;
|
||||||
if delete_operation.opstamp < committed_opstamp {
|
if delete_operation.opstamp < committed_opstamp {
|
||||||
let _index = &segment_updater.index;
|
let index = &segment_updater.index;
|
||||||
if let Err(e) =
|
let segment = index.segment(after_merge_segment_entry.meta().clone());
|
||||||
advance_deletes(&mut after_merge_segment_entry, committed_opstamp)
|
if let Err(e) = advance_deletes(
|
||||||
{
|
segment,
|
||||||
|
&mut after_merge_segment_entry,
|
||||||
|
committed_opstamp,
|
||||||
|
) {
|
||||||
error!(
|
error!(
|
||||||
"Merge of {:?} was cancelled (advancing deletes failed): {:?}",
|
"Merge of {:?} was cancelled (advancing deletes failed): {:?}",
|
||||||
merge_operation.segment_ids(),
|
merge_operation.segment_ids(),
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ use crate::schema::Schema;
|
|||||||
use crate::schema::Term;
|
use crate::schema::Term;
|
||||||
use crate::schema::Value;
|
use crate::schema::Value;
|
||||||
use crate::schema::{Field, FieldEntry};
|
use crate::schema::{Field, FieldEntry};
|
||||||
use crate::tokenizer::TokenizerManager;
|
use crate::tokenizer::{BoxTokenStream, PreTokenizedStream};
|
||||||
use crate::tokenizer::{BoxTokenStream, FacetTokenizer};
|
use crate::tokenizer::{FacetTokenizer, TextAnalyzer};
|
||||||
use crate::tokenizer::{PreTokenizedStream, TextAnalyzer};
|
|
||||||
use crate::tokenizer::{TokenStreamChain, Tokenizer};
|
use crate::tokenizer::{TokenStreamChain, Tokenizer};
|
||||||
use crate::DocId;
|
use crate::DocId;
|
||||||
use crate::Opstamp;
|
use crate::Opstamp;
|
||||||
@@ -65,12 +64,10 @@ impl SegmentWriter {
|
|||||||
memory_budget: usize,
|
memory_budget: usize,
|
||||||
mut segment: Segment,
|
mut segment: Segment,
|
||||||
schema: &Schema,
|
schema: &Schema,
|
||||||
tokenizer_manager: &TokenizerManager,
|
|
||||||
) -> crate::Result<SegmentWriter> {
|
) -> crate::Result<SegmentWriter> {
|
||||||
let table_num_bits = initial_table_size(memory_budget)?;
|
let table_num_bits = initial_table_size(memory_budget)?;
|
||||||
let segment_serializer = SegmentSerializer::for_segment(&mut segment)?;
|
let segment_serializer = SegmentSerializer::for_segment(&mut segment)?;
|
||||||
let multifield_postings = MultiFieldPostingsWriter::new(&schema, table_num_bits);
|
let multifield_postings = MultiFieldPostingsWriter::new(schema, table_num_bits);
|
||||||
|
|
||||||
let tokenizers = schema
|
let tokenizers = schema
|
||||||
.fields()
|
.fields()
|
||||||
.map(
|
.map(
|
||||||
@@ -79,7 +76,7 @@ impl SegmentWriter {
|
|||||||
.get_indexing_options()
|
.get_indexing_options()
|
||||||
.and_then(|text_index_option| {
|
.and_then(|text_index_option| {
|
||||||
let tokenizer_name = &text_index_option.tokenizer();
|
let tokenizer_name = &text_index_option.tokenizer();
|
||||||
tokenizer_manager.get(tokenizer_name)
|
segment.index().tokenizers().get(tokenizer_name)
|
||||||
}),
|
}),
|
||||||
_ => None,
|
_ => None,
|
||||||
},
|
},
|
||||||
@@ -88,9 +85,9 @@ impl SegmentWriter {
|
|||||||
Ok(SegmentWriter {
|
Ok(SegmentWriter {
|
||||||
max_doc: 0,
|
max_doc: 0,
|
||||||
multifield_postings,
|
multifield_postings,
|
||||||
fieldnorms_writer: FieldNormsWriter::for_schema(&schema),
|
fieldnorms_writer: FieldNormsWriter::for_schema(schema),
|
||||||
segment_serializer,
|
segment_serializer,
|
||||||
fast_field_writers: FastFieldsWriter::from_schema(&schema),
|
fast_field_writers: FastFieldsWriter::from_schema(schema),
|
||||||
doc_opstamps: Vec::with_capacity(1_000),
|
doc_opstamps: Vec::with_capacity(1_000),
|
||||||
tokenizers,
|
tokenizers,
|
||||||
})
|
})
|
||||||
|
|||||||
143
src/lib.rs
143
src/lib.rs
@@ -98,9 +98,6 @@
|
|||||||
//! [literate programming](https://tantivy-search.github.io/examples/basic_search.html) /
|
//! [literate programming](https://tantivy-search.github.io/examples/basic_search.html) /
|
||||||
//! [source code](https://github.com/tantivy-search/tantivy/blob/master/examples/basic_search.rs))
|
//! [source code](https://github.com/tantivy-search/tantivy/blob/master/examples/basic_search.rs))
|
||||||
|
|
||||||
#[macro_use]
|
|
||||||
extern crate serde_derive;
|
|
||||||
|
|
||||||
#[cfg_attr(test, macro_use)]
|
#[cfg_attr(test, macro_use)]
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
|
||||||
@@ -121,7 +118,6 @@ mod functional_test;
|
|||||||
mod macros;
|
mod macros;
|
||||||
|
|
||||||
pub use crate::error::TantivyError;
|
pub use crate::error::TantivyError;
|
||||||
pub use crate::error::TantivyError as Error;
|
|
||||||
pub use chrono;
|
pub use chrono;
|
||||||
|
|
||||||
/// Tantivy result.
|
/// Tantivy result.
|
||||||
@@ -174,6 +170,7 @@ pub use crate::schema::{Document, Term};
|
|||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Index format version.
|
/// Index format version.
|
||||||
const INDEX_FORMAT_VERSION: u32 = 1;
|
const INDEX_FORMAT_VERSION: u32 = 1;
|
||||||
@@ -272,6 +269,144 @@ impl DocAddress {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
mod sse2 {
|
||||||
|
use crate::postings::compression::{AlignedBuffer, COMPRESSION_BLOCK_SIZE};
|
||||||
|
use std::arch::x86_64::__m128i as DataType;
|
||||||
|
use std::arch::x86_64::_mm_add_epi32 as op_add;
|
||||||
|
use std::arch::x86_64::_mm_cmplt_epi32 as op_lt;
|
||||||
|
use std::arch::x86_64::_mm_load_si128 as op_load;
|
||||||
|
// requires 128-bits alignment
|
||||||
|
use std::arch::x86_64::_mm_set1_epi32 as set1;
|
||||||
|
use std::arch::x86_64::_mm_setzero_si128 as set0;
|
||||||
|
use std::arch::x86_64::_mm_sub_epi32 as op_sub;
|
||||||
|
use std::arch::x86_64::{_mm_cvtsi128_si32, _mm_shuffle_epi32};
|
||||||
|
|
||||||
|
const MASK1: i32 = 78;
|
||||||
|
const MASK2: i32 = 177;
|
||||||
|
|
||||||
|
/// Performs an exhaustive linear search over the
|
||||||
|
///
|
||||||
|
/// There is no early exit here. We simply count the
|
||||||
|
/// number of elements that are `< target`.
|
||||||
|
pub unsafe fn linear_search_sse2_128(arr: &AlignedBuffer, target: u32) -> usize {
|
||||||
|
let ptr = arr as *const AlignedBuffer as *const DataType;
|
||||||
|
let vkey = set1(target as i32);
|
||||||
|
let mut cnt = set0();
|
||||||
|
// We work over 4 `__m128i` at a time.
|
||||||
|
// A single `__m128i` actual contains 4 `u32`.
|
||||||
|
for i in 0..(COMPRESSION_BLOCK_SIZE as isize) / (4 * 4) {
|
||||||
|
let cmp1 = op_lt(op_load(ptr.offset(i * 4)), vkey);
|
||||||
|
let cmp2 = op_lt(op_load(ptr.offset(i * 4 + 1)), vkey);
|
||||||
|
let cmp3 = op_lt(op_load(ptr.offset(i * 4 + 2)), vkey);
|
||||||
|
let cmp4 = op_lt(op_load(ptr.offset(i * 4 + 3)), vkey);
|
||||||
|
let sum = op_add(op_add(cmp1, cmp2), op_add(cmp3, cmp4));
|
||||||
|
cnt = op_sub(cnt, sum);
|
||||||
|
}
|
||||||
|
cnt = op_add(cnt, _mm_shuffle_epi32(cnt, MASK1));
|
||||||
|
cnt = op_add(cnt, _mm_shuffle_epi32(cnt, MASK2));
|
||||||
|
_mm_cvtsi128_si32(cnt) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const MAX_DOC: u32 = 2_000_000_000u32;
|
||||||
|
|
||||||
|
use sse2::linear_search_sse2_128;
|
||||||
|
use crate::postings::BlockSegmentPostings;
|
||||||
|
use crate::schema::IndexRecordOption;
|
||||||
|
|
||||||
|
struct DocCursor {
|
||||||
|
block_segment_postings: BlockSegmentPostings,
|
||||||
|
cursor: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DocCursor {
|
||||||
|
fn new(mut block_segment_postings: BlockSegmentPostings) -> DocCursor {
|
||||||
|
block_segment_postings.advance();
|
||||||
|
DocCursor {
|
||||||
|
block_segment_postings,
|
||||||
|
cursor: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn doc(&self,) -> DocId {
|
||||||
|
self.block_segment_postings.doc(self.cursor)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len(&self,) -> usize {
|
||||||
|
self.block_segment_postings.doc_freq()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(&mut self) {
|
||||||
|
if self.cursor == 127 {
|
||||||
|
self.block_segment_postings.advance();
|
||||||
|
self.cursor = 0;
|
||||||
|
} else {
|
||||||
|
self.cursor += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn skip_to(&mut self, target: DocId) {
|
||||||
|
if self.block_segment_postings.skip_reader.doc() >= target {
|
||||||
|
unsafe {
|
||||||
|
let mut ptr = self.block_segment_postings.docs_aligned_b().0.get_unchecked(self.cursor) as *const u32;
|
||||||
|
while *ptr < target {
|
||||||
|
self.cursor += 1;
|
||||||
|
ptr = ptr.offset(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
self.block_segment_postings.skip_to_b(target);
|
||||||
|
let block= self.block_segment_postings.docs_aligned_b();
|
||||||
|
self.cursor = unsafe { linear_search_sse2_128(&block, target) };
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn intersection(idx: &InvertedIndexReader, terms: &[Term]) -> crate::Result<u32> {
|
||||||
|
let mut posting_lists: Vec<DocCursor> = Vec::with_capacity(terms.len());
|
||||||
|
for term in terms {
|
||||||
|
if let Some(mut block_postings) = idx.read_block_postings(term, IndexRecordOption::Basic) {
|
||||||
|
posting_lists.push(DocCursor::new(block_postings ));
|
||||||
|
} else {
|
||||||
|
return Ok(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
posting_lists.sort_by_key(|posting_list| posting_list.len());
|
||||||
|
Ok({ intersection_idx(&mut posting_lists[..]) })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn intersection_idx(posting_lists: &mut [DocCursor]) -> DocId {
|
||||||
|
let mut candidate = posting_lists[0].doc();
|
||||||
|
let mut i = 1;
|
||||||
|
|
||||||
|
let mut count = 0u32;
|
||||||
|
|
||||||
|
let num_posting_lists = posting_lists.len();
|
||||||
|
loop {
|
||||||
|
while i < num_posting_lists {
|
||||||
|
posting_lists[i].skip_to(candidate);
|
||||||
|
if posting_lists[i].doc() != candidate {
|
||||||
|
candidate = posting_lists[i].doc();
|
||||||
|
i = 0;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
count += 1;
|
||||||
|
if candidate == MAX_DOC {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
posting_lists[0].advance();
|
||||||
|
candidate = posting_lists[0].doc();
|
||||||
|
i = 1;
|
||||||
|
}
|
||||||
|
count - 1u32
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/// `DocAddress` contains all the necessary information
|
/// `DocAddress` contains all the necessary information
|
||||||
/// to identify a document given a `Searcher` object.
|
/// to identify a document given a `Searcher` object.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ mod sse2 {
|
|||||||
///
|
///
|
||||||
/// There is no early exit here. We simply count the
|
/// There is no early exit here. We simply count the
|
||||||
/// number of elements that are `< target`.
|
/// number of elements that are `< target`.
|
||||||
pub(crate) fn linear_search_sse2_128(arr: &AlignedBuffer, target: u32) -> usize {
|
pub fn linear_search_sse2_128(arr: &AlignedBuffer, target: u32) -> usize {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = arr as *const AlignedBuffer as *const DataType;
|
let ptr = arr as *const AlignedBuffer as *const DataType;
|
||||||
let vkey = set1(target as i32);
|
let vkey = set1(target as i32);
|
||||||
@@ -63,6 +63,8 @@ mod sse2 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub use self::sse2::linear_search_sse2_128;
|
||||||
|
|
||||||
/// This `linear search` browser exhaustively through the array.
|
/// This `linear search` browser exhaustively through the array.
|
||||||
/// but the early exit is very difficult to predict.
|
/// but the early exit is very difficult to predict.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ impl BlockEncoder {
|
|||||||
/// We ensure that the OutputBuffer is align on 128 bits
|
/// We ensure that the OutputBuffer is align on 128 bits
|
||||||
/// in order to run SSE2 linear search on it.
|
/// in order to run SSE2 linear search on it.
|
||||||
#[repr(align(128))]
|
#[repr(align(128))]
|
||||||
pub(crate) struct AlignedBuffer(pub [u32; COMPRESSION_BLOCK_SIZE]);
|
pub struct AlignedBuffer(pub [u32; COMPRESSION_BLOCK_SIZE]);
|
||||||
|
|
||||||
pub struct BlockDecoder {
|
pub struct BlockDecoder {
|
||||||
bitpacker: BitPacker4x,
|
bitpacker: BitPacker4x,
|
||||||
@@ -67,6 +67,18 @@ impl BlockDecoder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn uncompress_vint_sorted_b<'a>(
|
||||||
|
&mut self,
|
||||||
|
compressed_data: &'a [u8],
|
||||||
|
offset: u32,
|
||||||
|
num_els: usize,
|
||||||
|
) {
|
||||||
|
if num_els > 0 {
|
||||||
|
vint::uncompress_sorted(compressed_data, &mut self.output.0[..num_els], offset);
|
||||||
|
}
|
||||||
|
self.output.0[num_els..].iter_mut().for_each(|val| *val = 2_000_000_000u32);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn uncompress_block_sorted(
|
pub fn uncompress_block_sorted(
|
||||||
&mut self,
|
&mut self,
|
||||||
compressed_data: &[u8],
|
compressed_data: &[u8],
|
||||||
@@ -94,6 +106,11 @@ impl BlockDecoder {
|
|||||||
(&self.output, self.output_len)
|
(&self.output, self.output_len)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn output_aligned_2(&self) -> &AlignedBuffer {
|
||||||
|
&self.output
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn output(&self, idx: usize) -> u32 {
|
pub fn output(&self, idx: usize) -> u32 {
|
||||||
self.output.0[idx]
|
self.output.0[idx]
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ Postings module (also called inverted index)
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mod block_search;
|
mod block_search;
|
||||||
pub(crate) mod compression;
|
pub mod compression;
|
||||||
/// Postings module
|
/// Postings module
|
||||||
///
|
///
|
||||||
/// Postings, also called inverted lists, is the key datastructure
|
/// Postings, also called inverted lists, is the key datastructure
|
||||||
@@ -220,8 +220,7 @@ pub mod tests {
|
|||||||
|
|
||||||
{
|
{
|
||||||
let mut segment_writer =
|
let mut segment_writer =
|
||||||
SegmentWriter::for_segment(3_000_000, segment.clone(), &schema, index.tokenizers())
|
SegmentWriter::for_segment(3_000_000, segment.clone(), &schema).unwrap();
|
||||||
.unwrap();
|
|
||||||
{
|
{
|
||||||
let mut doc = Document::default();
|
let mut doc = Document::default();
|
||||||
// checking that position works if the field has two values
|
// checking that position works if the field has two values
|
||||||
|
|||||||
@@ -317,7 +317,7 @@ pub struct BlockSegmentPostings {
|
|||||||
num_vint_docs: usize,
|
num_vint_docs: usize,
|
||||||
|
|
||||||
remaining_data: OwnedRead,
|
remaining_data: OwnedRead,
|
||||||
skip_reader: SkipReader,
|
pub skip_reader: SkipReader,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_into_skips_and_postings(
|
fn split_into_skips_and_postings(
|
||||||
@@ -414,7 +414,12 @@ impl BlockSegmentPostings {
|
|||||||
self.doc_decoder.output_array()
|
self.doc_decoder.output_array()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn docs_aligned(&self) -> (&AlignedBuffer, usize) {
|
#[inline(always)]
|
||||||
|
pub fn docs_aligned_b(&self) -> &AlignedBuffer {
|
||||||
|
self.doc_decoder.output_aligned_2()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn docs_aligned(&self) -> (&AlignedBuffer, usize) {
|
||||||
self.doc_decoder.output_aligned()
|
self.doc_decoder.output_aligned()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -486,6 +491,7 @@ impl BlockSegmentPostings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.doc_offset = self.skip_reader.doc();
|
self.doc_offset = self.skip_reader.doc();
|
||||||
|
unsafe { core::arch::x86_64::_mm_prefetch(self.remaining_data.as_ref() as *mut i8, _MM_HINT_T1) };
|
||||||
return BlockSegmentPostingsSkipResult::Success(skip_freqs);
|
return BlockSegmentPostingsSkipResult::Success(skip_freqs);
|
||||||
} else {
|
} else {
|
||||||
skip_freqs += self.skip_reader.tf_sum();
|
skip_freqs += self.skip_reader.tf_sum();
|
||||||
@@ -526,6 +532,41 @@ impl BlockSegmentPostings {
|
|||||||
BlockSegmentPostingsSkipResult::Terminated
|
BlockSegmentPostingsSkipResult::Terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ensure we are located on the right block.
|
||||||
|
#[inline(never)]
|
||||||
|
pub fn skip_to_b(&mut self, target_doc: DocId) {
|
||||||
|
while self.skip_reader.advance() {
|
||||||
|
if self.skip_reader.doc() >= target_doc {
|
||||||
|
// the last document of the current block is larger
|
||||||
|
// than the target.
|
||||||
|
//
|
||||||
|
// We found our block!
|
||||||
|
let num_bits = self.skip_reader.doc_num_bits();
|
||||||
|
let num_consumed_bytes = self.doc_decoder.uncompress_block_sorted(
|
||||||
|
self.remaining_data.as_ref(),
|
||||||
|
self.doc_offset,
|
||||||
|
num_bits,
|
||||||
|
);
|
||||||
|
let tf_num_bits = self.skip_reader.tf_num_bits();
|
||||||
|
let num_bytes_to_skip = compressed_block_size(tf_num_bits);
|
||||||
|
self.remaining_data.advance(num_consumed_bytes + num_bytes_to_skip);
|
||||||
|
self.doc_offset = self.skip_reader.doc();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
let advance_len = self.skip_reader.total_block_len();
|
||||||
|
self.doc_offset = self.skip_reader.doc();
|
||||||
|
self.remaining_data.advance(advance_len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we are now on the last, incomplete, variable encoded block.
|
||||||
|
self.doc_decoder.uncompress_vint_sorted_b(
|
||||||
|
self.remaining_data.as_ref(),
|
||||||
|
self.doc_offset,
|
||||||
|
self.num_vint_docs
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Advance to the next block.
|
/// Advance to the next block.
|
||||||
///
|
///
|
||||||
/// Returns false iff there was no remaining blocks.
|
/// Returns false iff there was no remaining blocks.
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ impl SkipSerializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SkipReader {
|
pub struct SkipReader {
|
||||||
doc: DocId,
|
doc: DocId,
|
||||||
owned_read: OwnedRead,
|
owned_read: OwnedRead,
|
||||||
doc_num_bits: u8,
|
doc_num_bits: u8,
|
||||||
|
|||||||
@@ -31,24 +31,11 @@ mod tests {
|
|||||||
// writing the segment
|
// writing the segment
|
||||||
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
let mut index_writer = index.writer_with_num_threads(1, 3_000_000).unwrap();
|
||||||
{
|
{
|
||||||
let doc = doc!(text_field => "a b c");
|
index_writer.add_document(doc!(text_field => "a b c"));
|
||||||
index_writer.add_document(doc);
|
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"));
|
||||||
let doc = doc!(text_field => "a c");
|
index_writer.add_document(doc!(text_field => "d"));
|
||||||
index_writer.add_document(doc);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let doc = doc!(text_field => "b c");
|
|
||||||
index_writer.add_document(doc);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let doc = doc!(text_field => "a b c d");
|
|
||||||
index_writer.add_document(doc);
|
|
||||||
}
|
|
||||||
{
|
|
||||||
let doc = doc!(text_field => "d");
|
|
||||||
index_writer.add_document(doc);
|
|
||||||
}
|
}
|
||||||
assert!(index_writer.commit().is_ok());
|
assert!(index_writer.commit().is_ok());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::{DocId, TantivyError};
|
use crate::{DocId, TantivyError};
|
||||||
|
use serde::Serialize;
|
||||||
|
|
||||||
pub(crate) fn does_not_match(doc: DocId) -> TantivyError {
|
pub(crate) fn does_not_match(doc: DocId) -> TantivyError {
|
||||||
TantivyError::InvalidArgument(format!("Document #({}) does not match", doc))
|
TantivyError::InvalidArgument(format!("Document #({}) does not match", doc))
|
||||||
|
|||||||
@@ -2,10 +2,36 @@ use crate::query::{AutomatonWeight, Query, Weight};
|
|||||||
use crate::schema::Term;
|
use crate::schema::Term;
|
||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
use crate::TantivyError::InvalidArgument;
|
use crate::TantivyError::InvalidArgument;
|
||||||
use levenshtein_automata::{LevenshteinAutomatonBuilder, DFA};
|
use levenshtein_automata::{Distance, LevenshteinAutomatonBuilder, DFA};
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::ops::Range;
|
use std::ops::Range;
|
||||||
|
use tantivy_fst::Automaton;
|
||||||
|
|
||||||
|
pub(crate) struct DFAWrapper(pub DFA);
|
||||||
|
|
||||||
|
impl Automaton for DFAWrapper {
|
||||||
|
type State = u32;
|
||||||
|
|
||||||
|
fn start(&self) -> Self::State {
|
||||||
|
self.0.initial_state()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_match(&self, state: &Self::State) -> bool {
|
||||||
|
match self.0.distance(*state) {
|
||||||
|
Distance::Exact(_) => true,
|
||||||
|
Distance::AtLeast(_) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_match(&self, state: &u32) -> bool {
|
||||||
|
*state != levenshtein_automata::SINK_STATE
|
||||||
|
}
|
||||||
|
|
||||||
|
fn accept(&self, state: &Self::State, byte: u8) -> Self::State {
|
||||||
|
self.0.transition(*state, byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A range of Levenshtein distances that we will build DFAs for our terms
|
/// A range of Levenshtein distances that we will build DFAs for our terms
|
||||||
/// The computation is exponential, so best keep it to low single digits
|
/// The computation is exponential, so best keep it to low single digits
|
||||||
@@ -101,13 +127,20 @@ impl FuzzyTermQuery {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn specialized_weight(&self) -> crate::Result<AutomatonWeight<DFA>> {
|
fn specialized_weight(&self) -> crate::Result<AutomatonWeight<DFAWrapper>> {
|
||||||
// LEV_BUILDER is a HashMap, whose `get` method returns an Option
|
// LEV_BUILDER is a HashMap, whose `get` method returns an Option
|
||||||
match LEV_BUILDER.get(&(self.distance, false)) {
|
match LEV_BUILDER.get(&(self.distance, false)) {
|
||||||
// Unwrap the option and build the Ok(AutomatonWeight)
|
// Unwrap the option and build the Ok(AutomatonWeight)
|
||||||
Some(automaton_builder) => {
|
Some(automaton_builder) => {
|
||||||
let automaton = automaton_builder.build_dfa(self.term.text());
|
let automaton = if self.prefix {
|
||||||
Ok(AutomatonWeight::new(self.term.field(), automaton))
|
automaton_builder.build_prefix_dfa(self.term.text())
|
||||||
|
} else {
|
||||||
|
automaton_builder.build_dfa(self.term.text())
|
||||||
|
};
|
||||||
|
Ok(AutomatonWeight::new(
|
||||||
|
self.term.field(),
|
||||||
|
DFAWrapper(automaton),
|
||||||
|
))
|
||||||
}
|
}
|
||||||
None => Err(InvalidArgument(format!(
|
None => Err(InvalidArgument(format!(
|
||||||
"Levenshtein distance of {} is not allowed. Choose a value in the {:?} range",
|
"Levenshtein distance of {} is not allowed. Choose a value in the {:?} range",
|
||||||
@@ -166,5 +199,17 @@ mod test {
|
|||||||
let (score, _) = top_docs[0];
|
let (score, _) = top_docs[0];
|
||||||
assert_nearly_equals(1f32, score);
|
assert_nearly_equals(1f32, score);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let term = Term::from_field_text(country_field, "jap");
|
||||||
|
|
||||||
|
let fuzzy_query = FuzzyTermQuery::new_prefix(term, 1, true);
|
||||||
|
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(1f32, score);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ mod term_query;
|
|||||||
mod union;
|
mod union;
|
||||||
mod weight;
|
mod weight;
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod vec_docset;
|
mod vec_docset;
|
||||||
|
|
||||||
@@ -43,6 +44,7 @@ pub use self::empty_query::{EmptyQuery, EmptyScorer, EmptyWeight};
|
|||||||
pub use self::exclude::Exclude;
|
pub use self::exclude::Exclude;
|
||||||
pub use self::explanation::Explanation;
|
pub use self::explanation::Explanation;
|
||||||
pub use self::fuzzy_query::FuzzyTermQuery;
|
pub use self::fuzzy_query::FuzzyTermQuery;
|
||||||
|
pub(crate) use self::fuzzy_query::DFAWrapper;
|
||||||
pub use self::intersection::intersect_scorers;
|
pub use self::intersection::intersect_scorers;
|
||||||
pub use self::phrase_query::PhraseQuery;
|
pub use self::phrase_query::PhraseQuery;
|
||||||
pub use self::query::Query;
|
pub use self::query::Query;
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ pub mod tests {
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::collector::tests::{TEST_COLLECTOR_WITHOUT_SCORE, TEST_COLLECTOR_WITH_SCORE};
|
use crate::collector::tests::{TEST_COLLECTOR_WITHOUT_SCORE, TEST_COLLECTOR_WITH_SCORE};
|
||||||
use crate::core::Index;
|
use crate::core::Index;
|
||||||
use crate::error::TantivyError;
|
|
||||||
use crate::schema::{Schema, Term, TEXT};
|
use crate::schema::{Schema, Term, TEXT};
|
||||||
use crate::tests::assert_nearly_equals;
|
use crate::tests::assert_nearly_equals;
|
||||||
use crate::DocAddress;
|
use crate::DocAddress;
|
||||||
@@ -127,21 +126,16 @@ pub mod tests {
|
|||||||
Term::from_field_text(text_field, "a"),
|
Term::from_field_text(text_field, "a"),
|
||||||
Term::from_field_text(text_field, "b"),
|
Term::from_field_text(text_field, "b"),
|
||||||
]);
|
]);
|
||||||
match searcher
|
|
||||||
|
let search_result = searcher
|
||||||
.search(&phrase_query, &TEST_COLLECTOR_WITH_SCORE)
|
.search(&phrase_query, &TEST_COLLECTOR_WITH_SCORE)
|
||||||
.map(|_| ())
|
.map(|_| ());
|
||||||
.unwrap_err()
|
assert!(matches!(
|
||||||
{
|
search_result,
|
||||||
TantivyError::SchemaError(ref msg) => {
|
Err(crate::TantivyError::SchemaError(msg))
|
||||||
assert_eq!(
|
if msg == "Applied phrase query on field \"text\", which does not have positions \
|
||||||
"Applied phrase query on field \"text\", which does not have positions indexed",
|
indexed"
|
||||||
msg.as_str()
|
));
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("Should have returned an error");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -1,11 +1,17 @@
|
|||||||
use super::Weight;
|
use super::Weight;
|
||||||
use crate::core::searcher::Searcher;
|
use crate::core::searcher::Searcher;
|
||||||
use crate::query::Explanation;
|
use crate::query::Explanation;
|
||||||
use crate::DocAddress;
|
use crate::{DocAddress, Score};
|
||||||
use crate::Term;
|
use crate::Term;
|
||||||
use downcast_rs::impl_downcast;
|
use downcast_rs::impl_downcast;
|
||||||
use std::collections::BTreeSet;
|
use std::collections::BTreeSet;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
use crate::collector::{TopDocs, Collector, SegmentCollector, Count};
|
||||||
|
|
||||||
|
pub struct TopKResult {
|
||||||
|
pub count: u64,
|
||||||
|
docs: Vec<(Score, DocAddress)>
|
||||||
|
}
|
||||||
|
|
||||||
/// The `Query` trait defines a set of documents and a scoring method
|
/// The `Query` trait defines a set of documents and a scoring method
|
||||||
/// for those documents.
|
/// for those documents.
|
||||||
@@ -56,6 +62,27 @@ pub trait Query: QueryClone + downcast_rs::Downcast + fmt::Debug {
|
|||||||
weight.explain(reader, doc_address.doc())
|
weight.explain(reader, doc_address.doc())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn top_k(&self, searcher: &Searcher, num_hits: usize) -> crate::Result<TopKResult> {
|
||||||
|
let top_docs = TopDocs::with_limit(num_hits);
|
||||||
|
let collector = (Count, top_docs);
|
||||||
|
let weight = self.weight(searcher, false)?;
|
||||||
|
let mut count = 0u64;
|
||||||
|
let mut result = 0;
|
||||||
|
let mut child_fruits = Vec::with_capacity(searcher.segment_readers().len());
|
||||||
|
for (segment_ord, reader) in searcher.segment_readers().iter().enumerate() {
|
||||||
|
let mut child_top_k = collector.for_segment(segment_ord as u32, reader)?;
|
||||||
|
let mut scorer = weight.scorer(reader, 1.0f32)?;
|
||||||
|
// handle deletes
|
||||||
|
scorer.top_k(&mut child_top_k);
|
||||||
|
child_fruits.push(child_top_k.harvest());
|
||||||
|
}
|
||||||
|
let (count, docs) = collector.merge_fruits(child_fruits)?;
|
||||||
|
Ok(TopKResult {
|
||||||
|
count: count as u64,
|
||||||
|
docs
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the number of documents matching the query.
|
/// Returns the number of documents matching the query.
|
||||||
fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
|
fn count(&self, searcher: &Searcher) -> crate::Result<usize> {
|
||||||
let weight = self.weight(searcher, false)?;
|
let weight = self.weight(searcher, false)?;
|
||||||
|
|||||||
@@ -174,6 +174,16 @@ pub struct QueryParser {
|
|||||||
boost: HashMap<Field, f32>,
|
boost: HashMap<Field, f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn all_negative(ast: &LogicalAST) -> bool {
|
||||||
|
match ast {
|
||||||
|
LogicalAST::Leaf(_) => false,
|
||||||
|
LogicalAST::Boost(ref child_ast, _) => all_negative(&*child_ast),
|
||||||
|
LogicalAST::Clause(children) => children
|
||||||
|
.iter()
|
||||||
|
.all(|(ref occur, child)| (*occur == Occur::MustNot) || all_negative(child)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl QueryParser {
|
impl QueryParser {
|
||||||
/// Creates a `QueryParser`, given
|
/// Creates a `QueryParser`, given
|
||||||
/// * schema - index Schema
|
/// * schema - index Schema
|
||||||
@@ -253,8 +263,13 @@ impl QueryParser {
|
|||||||
&self,
|
&self,
|
||||||
user_input_ast: UserInputAST,
|
user_input_ast: UserInputAST,
|
||||||
) -> Result<LogicalAST, QueryParserError> {
|
) -> Result<LogicalAST, QueryParserError> {
|
||||||
let (occur, ast) = self.compute_logical_ast_with_occur(user_input_ast)?;
|
let ast = self.compute_logical_ast_with_occur(user_input_ast)?;
|
||||||
if occur == Occur::MustNot {
|
if let LogicalAST::Clause(children) = &ast {
|
||||||
|
if children.is_empty() {
|
||||||
|
return Ok(ast);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if all_negative(&ast) {
|
||||||
return Err(QueryParserError::AllButQueryForbidden);
|
return Err(QueryParserError::AllButQueryForbidden);
|
||||||
}
|
}
|
||||||
Ok(ast)
|
Ok(ast)
|
||||||
@@ -410,31 +425,23 @@ impl QueryParser {
|
|||||||
fn compute_logical_ast_with_occur(
|
fn compute_logical_ast_with_occur(
|
||||||
&self,
|
&self,
|
||||||
user_input_ast: UserInputAST,
|
user_input_ast: UserInputAST,
|
||||||
) -> Result<(Occur, LogicalAST), QueryParserError> {
|
) -> Result<LogicalAST, QueryParserError> {
|
||||||
match user_input_ast {
|
match user_input_ast {
|
||||||
UserInputAST::Clause(sub_queries) => {
|
UserInputAST::Clause(sub_queries) => {
|
||||||
let default_occur = self.default_occur();
|
let default_occur = self.default_occur();
|
||||||
let mut logical_sub_queries: Vec<(Occur, LogicalAST)> = Vec::new();
|
let mut logical_sub_queries: Vec<(Occur, LogicalAST)> = Vec::new();
|
||||||
for sub_query in sub_queries {
|
for (occur_opt, sub_ast) in sub_queries {
|
||||||
let (occur, sub_ast) = self.compute_logical_ast_with_occur(sub_query)?;
|
let sub_ast = self.compute_logical_ast_with_occur(sub_ast)?;
|
||||||
let new_occur = Occur::compose(default_occur, occur);
|
let occur = occur_opt.unwrap_or(default_occur);
|
||||||
logical_sub_queries.push((new_occur, sub_ast));
|
logical_sub_queries.push((occur, sub_ast));
|
||||||
}
|
}
|
||||||
Ok((Occur::Should, LogicalAST::Clause(logical_sub_queries)))
|
Ok(LogicalAST::Clause(logical_sub_queries))
|
||||||
}
|
|
||||||
UserInputAST::Unary(left_occur, subquery) => {
|
|
||||||
let (right_occur, logical_sub_queries) =
|
|
||||||
self.compute_logical_ast_with_occur(*subquery)?;
|
|
||||||
Ok((Occur::compose(left_occur, right_occur), logical_sub_queries))
|
|
||||||
}
|
}
|
||||||
UserInputAST::Boost(ast, boost) => {
|
UserInputAST::Boost(ast, boost) => {
|
||||||
let (occur, ast_without_occur) = self.compute_logical_ast_with_occur(*ast)?;
|
let ast = self.compute_logical_ast_with_occur(*ast)?;
|
||||||
Ok((occur, ast_without_occur.boost(boost)))
|
Ok(ast.boost(boost))
|
||||||
}
|
|
||||||
UserInputAST::Leaf(leaf) => {
|
|
||||||
let result_ast = self.compute_logical_ast_from_leaf(*leaf)?;
|
|
||||||
Ok((Occur::Should, result_ast))
|
|
||||||
}
|
}
|
||||||
|
UserInputAST::Leaf(leaf) => self.compute_logical_ast_from_leaf(*leaf),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -782,6 +789,20 @@ mod test {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_parse_query_to_ast_ab_c() {
|
||||||
|
test_parse_query_to_logical_ast_helper(
|
||||||
|
"(+title:a +title:b) title:c",
|
||||||
|
"((+Term(field=0,bytes=[97]) +Term(field=0,bytes=[98])) Term(field=0,bytes=[99]))",
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
test_parse_query_to_logical_ast_helper(
|
||||||
|
"(+title:a +title:b) title:c",
|
||||||
|
"(+(+Term(field=0,bytes=[97]) +Term(field=0,bytes=[98])) +Term(field=0,bytes=[99]))",
|
||||||
|
true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_parse_query_to_ast_single_term() {
|
pub fn test_parse_query_to_ast_single_term() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
@@ -801,11 +822,13 @@ mod test {
|
|||||||
Term(field=1,bytes=[116, 105, 116, 105])))",
|
Term(field=1,bytes=[116, 105, 116, 105])))",
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
}
|
||||||
parse_query_to_logical_ast("-title:toto", false)
|
|
||||||
.err()
|
#[test]
|
||||||
.unwrap(),
|
fn test_single_negative_term() {
|
||||||
QueryParserError::AllButQueryForbidden
|
assert_matches!(
|
||||||
|
parse_query_to_logical_ast("-title:toto", false),
|
||||||
|
Err(QueryParserError::AllButQueryForbidden)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -965,6 +988,18 @@ mod test {
|
|||||||
assert!(query_parser.parse_query("with_stop_words:the").is_ok());
|
assert!(query_parser.parse_query("with_stop_words:the").is_ok());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
pub fn test_parse_query_single_negative_term_through_error() {
|
||||||
|
assert_matches!(
|
||||||
|
parse_query_to_logical_ast("-title:toto", true),
|
||||||
|
Err(QueryParserError::AllButQueryForbidden)
|
||||||
|
);
|
||||||
|
assert_matches!(
|
||||||
|
parse_query_to_logical_ast("-title:toto", false),
|
||||||
|
Err(QueryParserError::AllButQueryForbidden)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
pub fn test_parse_query_to_ast_conjunction() {
|
pub fn test_parse_query_to_ast_conjunction() {
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
@@ -984,12 +1019,6 @@ mod test {
|
|||||||
Term(field=1,bytes=[116, 105, 116, 105])))",
|
Term(field=1,bytes=[116, 105, 116, 105])))",
|
||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
assert_eq!(
|
|
||||||
parse_query_to_logical_ast("-title:toto", true)
|
|
||||||
.err()
|
|
||||||
.unwrap(),
|
|
||||||
QueryParserError::AllButQueryForbidden
|
|
||||||
);
|
|
||||||
test_parse_query_to_logical_ast_helper(
|
test_parse_query_to_logical_ast_helper(
|
||||||
"title:a b",
|
"title:a b",
|
||||||
"(+Term(field=0,bytes=[97]) \
|
"(+Term(field=0,bytes=[97]) \
|
||||||
@@ -1013,4 +1042,26 @@ mod test {
|
|||||||
false
|
false
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_and_default_regardless_of_default_conjunctive() {
|
||||||
|
for &default_conjunction in &[false, true] {
|
||||||
|
test_parse_query_to_logical_ast_helper(
|
||||||
|
"title:a AND title:b",
|
||||||
|
"(+Term(field=0,bytes=[97]) +Term(field=0,bytes=[98]))",
|
||||||
|
default_conjunction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_or_default_conjunctive() {
|
||||||
|
for &default_conjunction in &[false, true] {
|
||||||
|
test_parse_query_to_logical_ast_helper(
|
||||||
|
"title:a OR title:b",
|
||||||
|
"(Term(field=0,bytes=[97]) Term(field=0,bytes=[98]))",
|
||||||
|
default_conjunction,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ use crate::DocId;
|
|||||||
use crate::Score;
|
use crate::Score;
|
||||||
use downcast_rs::impl_downcast;
|
use downcast_rs::impl_downcast;
|
||||||
use std::ops::DerefMut;
|
use std::ops::DerefMut;
|
||||||
|
use crate::collector::{SegmentCollector, Collector, TopDocs, Count};
|
||||||
|
|
||||||
|
|
||||||
/// Scored set of documents matching a query within a specific segment.
|
/// Scored set of documents matching a query within a specific segment.
|
||||||
///
|
///
|
||||||
@@ -21,6 +23,12 @@ pub trait Scorer: downcast_rs::Downcast + DocSet + 'static {
|
|||||||
callback(self.doc(), self.score());
|
callback(self.doc(), self.score());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn top_k(&mut self, collector: &mut <(Count, TopDocs) as Collector>::Child) {
|
||||||
|
while self.advance() {
|
||||||
|
collector.collect(self.doc(), self.score());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_downcast!(Scorer);
|
impl_downcast!(Scorer);
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ mod tests {
|
|||||||
let term_a = Term::from_field_text(text_field, "a");
|
let term_a = Term::from_field_text(text_field, "a");
|
||||||
let term_query = TermQuery::new(term_a, IndexRecordOption::Basic);
|
let term_query = TermQuery::new(term_a, IndexRecordOption::Basic);
|
||||||
let reader = index.reader().unwrap();
|
let reader = index.reader().unwrap();
|
||||||
assert_eq!(reader.searcher().segment_readers().len(), 1);
|
|
||||||
assert_eq!(term_query.count(&*reader.searcher()).unwrap(), 1);
|
assert_eq!(term_query.count(&*reader.searcher()).unwrap(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,83 +0,0 @@
|
|||||||
use crate::directory::{WatchCallbackList, WatchHandle};
|
|
||||||
use crate::indexer::SegmentRegisters;
|
|
||||||
use crate::reader::pool::Pool;
|
|
||||||
use crate::{Index, LeasedItem, Searcher, Segment, SegmentReader};
|
|
||||||
use std::iter::repeat_with;
|
|
||||||
use std::sync::{Arc, RwLock, Weak};
|
|
||||||
|
|
||||||
struct InnerNRTReader {
|
|
||||||
num_searchers: usize,
|
|
||||||
index: Index,
|
|
||||||
searcher_pool: Pool<Searcher>,
|
|
||||||
segment_registers: Arc<RwLock<SegmentRegisters>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl InnerNRTReader {
|
|
||||||
fn load_segment_readers(&self) -> crate::Result<Vec<SegmentReader>> {
|
|
||||||
let segments: Vec<Segment> = {
|
|
||||||
let rlock = self.segment_registers.read().unwrap();
|
|
||||||
rlock.committed_segment()
|
|
||||||
};
|
|
||||||
segments
|
|
||||||
.iter()
|
|
||||||
.map(SegmentReader::open)
|
|
||||||
.collect::<crate::Result<Vec<SegmentReader>>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn reload(&self) -> crate::Result<()> {
|
|
||||||
let segment_readers: Vec<SegmentReader> = self.load_segment_readers()?;
|
|
||||||
let schema = self.index.schema();
|
|
||||||
let searchers = repeat_with(|| {
|
|
||||||
Searcher::new(schema.clone(), self.index.clone(), segment_readers.clone())
|
|
||||||
})
|
|
||||||
.take(self.num_searchers)
|
|
||||||
.collect();
|
|
||||||
self.searcher_pool.publish_new_generation(searchers);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn searcher(&self) -> LeasedItem<Searcher> {
|
|
||||||
self.searcher_pool.acquire()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct NRTReader {
|
|
||||||
inner: Arc<InnerNRTReader>,
|
|
||||||
watch_handle: WatchHandle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl NRTReader {
|
|
||||||
pub fn reload(&self) -> crate::Result<()> {
|
|
||||||
self.inner.reload()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn searcher(&self) -> LeasedItem<Searcher> {
|
|
||||||
self.inner.searcher()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn create(
|
|
||||||
num_searchers: usize,
|
|
||||||
index: Index,
|
|
||||||
segment_registers: Arc<RwLock<SegmentRegisters>>,
|
|
||||||
watch_callback_list: &WatchCallbackList,
|
|
||||||
) -> crate::Result<Self> {
|
|
||||||
let inner_reader: Arc<InnerNRTReader> = Arc::new(InnerNRTReader {
|
|
||||||
num_searchers,
|
|
||||||
index,
|
|
||||||
searcher_pool: Pool::new(),
|
|
||||||
segment_registers,
|
|
||||||
});
|
|
||||||
let inner_reader_weak: Weak<InnerNRTReader> = Arc::downgrade(&inner_reader);
|
|
||||||
let watch_handle = watch_callback_list.subscribe(Box::new(move || {
|
|
||||||
if let Some(nrt_reader_arc) = inner_reader_weak.upgrade() {
|
|
||||||
let _ = nrt_reader_arc.reload();
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
inner_reader.reload()?;
|
|
||||||
Ok(NRTReader {
|
|
||||||
inner: inner_reader,
|
|
||||||
watch_handle,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,180 +0,0 @@
|
|||||||
use super::pool::Pool;
|
|
||||||
use crate::core::Segment;
|
|
||||||
use crate::directory::Directory;
|
|
||||||
use crate::directory::WatchHandle;
|
|
||||||
use crate::directory::META_LOCK;
|
|
||||||
use crate::Searcher;
|
|
||||||
use crate::SegmentReader;
|
|
||||||
use crate::{Index, LeasedItem};
|
|
||||||
use crate::{IndexReader, Result};
|
|
||||||
use std::iter::repeat_with;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
/// Defines when a new version of the index should be reloaded.
|
|
||||||
///
|
|
||||||
/// Regardless of whether you search and index in the same process, tantivy does not necessarily
|
|
||||||
/// reflects the change that are commited to your index. `ReloadPolicy` precisely helps you define
|
|
||||||
/// when you want your index to be reloaded.
|
|
||||||
#[derive(Clone, Copy)]
|
|
||||||
pub enum ReloadPolicy {
|
|
||||||
/// The index is entirely reloaded manually.
|
|
||||||
/// All updates of the index should be manual.
|
|
||||||
///
|
|
||||||
/// No change is reflected automatically. You are required to call `.load_seacher()` manually.
|
|
||||||
Manual,
|
|
||||||
/// The index is reloaded within milliseconds after a new commit is available.
|
|
||||||
/// This is made possible by watching changes in the `meta.json` file.
|
|
||||||
OnCommit, // TODO add NEAR_REAL_TIME(target_ms)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `IndexReader` builder
|
|
||||||
///
|
|
||||||
/// It makes it possible to set the following values.
|
|
||||||
///
|
|
||||||
/// - `num_searchers` (by default, the number of detected CPU threads):
|
|
||||||
///
|
|
||||||
/// When `num_searchers` queries are requested at the same time, the `num_searchers` will block
|
|
||||||
/// until the one of the searcher in-use gets released.
|
|
||||||
/// - `reload_policy` (by default `ReloadPolicy::OnCommit`):
|
|
||||||
///
|
|
||||||
/// See [`ReloadPolicy`](./enum.ReloadPolicy.html) for more details.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct IndexReaderBuilder {
|
|
||||||
num_searchers: usize,
|
|
||||||
reload_policy: ReloadPolicy,
|
|
||||||
index: Index,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IndexReaderBuilder {
|
|
||||||
pub(crate) fn new(index: Index) -> IndexReaderBuilder {
|
|
||||||
IndexReaderBuilder {
|
|
||||||
num_searchers: num_cpus::get(),
|
|
||||||
reload_policy: ReloadPolicy::Manual,
|
|
||||||
index,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Builds the reader.
|
|
||||||
///
|
|
||||||
/// Building the reader is a non-trivial operation that requires
|
|
||||||
/// to open different segment readers. It may take hundreds of milliseconds
|
|
||||||
/// of time and it may return an error.
|
|
||||||
/// TODO(pmasurel) Use the `TryInto` trait once it is available in stable.
|
|
||||||
pub fn try_into(self) -> Result<IndexReader> {
|
|
||||||
let inner_reader = MetaFileIndexReaderInner {
|
|
||||||
index: self.index,
|
|
||||||
num_searchers: self.num_searchers,
|
|
||||||
searcher_pool: Pool::new(),
|
|
||||||
};
|
|
||||||
inner_reader.reload()?;
|
|
||||||
let inner_reader_arc = Arc::new(inner_reader);
|
|
||||||
let watch_handle_opt: Option<WatchHandle>;
|
|
||||||
match self.reload_policy {
|
|
||||||
ReloadPolicy::Manual => {
|
|
||||||
// No need to set anything...
|
|
||||||
watch_handle_opt = None;
|
|
||||||
}
|
|
||||||
ReloadPolicy::OnCommit => {
|
|
||||||
let inner_reader_arc_clone = inner_reader_arc.clone();
|
|
||||||
let callback = move || {
|
|
||||||
if let Err(err) = inner_reader_arc_clone.reload() {
|
|
||||||
error!(
|
|
||||||
"Error while loading searcher after commit was detected. {:?}",
|
|
||||||
err
|
|
||||||
);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let watch_handle = inner_reader_arc
|
|
||||||
.index
|
|
||||||
.directory()
|
|
||||||
.watch(Box::new(callback))?;
|
|
||||||
watch_handle_opt = Some(watch_handle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(IndexReader::from(MetaFileIndexReader {
|
|
||||||
inner: inner_reader_arc,
|
|
||||||
watch_handle_opt,
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the reload_policy.
|
|
||||||
///
|
|
||||||
/// See [`ReloadPolicy`](./enum.ReloadPolicy.html) for more details.
|
|
||||||
pub fn reload_policy(mut self, reload_policy: ReloadPolicy) -> IndexReaderBuilder {
|
|
||||||
self.reload_policy = reload_policy;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Sets the number of `Searcher` in the searcher pool.
|
|
||||||
pub fn num_searchers(mut self, num_searchers: usize) -> IndexReaderBuilder {
|
|
||||||
self.num_searchers = num_searchers;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MetaFileIndexReaderInner {
|
|
||||||
num_searchers: usize,
|
|
||||||
searcher_pool: Pool<Searcher>,
|
|
||||||
index: Index,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaFileIndexReaderInner {
|
|
||||||
fn load_segment_readers(&self) -> crate::Result<Vec<SegmentReader>> {
|
|
||||||
// We keep the lock until we have effectively finished opening the
|
|
||||||
// the `SegmentReader` because it prevents a diffferent process
|
|
||||||
// to garbage collect these file while we open them.
|
|
||||||
//
|
|
||||||
// Once opened, on linux & mac, the mmap will remain valid after
|
|
||||||
// the file has been deleted
|
|
||||||
// On windows, the file deletion will fail.
|
|
||||||
let _meta_lock = self.index.directory().acquire_lock(&META_LOCK)?;
|
|
||||||
let searchable_segments = self.searchable_segments()?;
|
|
||||||
searchable_segments
|
|
||||||
.iter()
|
|
||||||
.map(SegmentReader::open)
|
|
||||||
.collect::<Result<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload(&self) -> crate::Result<()> {
|
|
||||||
let segment_readers: Vec<SegmentReader> = self.load_segment_readers()?;
|
|
||||||
let schema = self.index.schema();
|
|
||||||
let searchers = repeat_with(|| {
|
|
||||||
Searcher::new(schema.clone(), self.index.clone(), segment_readers.clone())
|
|
||||||
})
|
|
||||||
.take(self.num_searchers)
|
|
||||||
.collect();
|
|
||||||
self.searcher_pool.publish_new_generation(searchers);
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Returns the list of segments that are searchable
|
|
||||||
fn searchable_segments(&self) -> crate::Result<Vec<Segment>> {
|
|
||||||
self.index.searchable_segments()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn searcher(&self) -> LeasedItem<Searcher> {
|
|
||||||
self.searcher_pool.acquire()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// `IndexReader` is your entry point to read and search the index.
|
|
||||||
///
|
|
||||||
/// It controls when a new version of the index should be loaded and lends
|
|
||||||
/// you instances of `Searcher` for the last loaded version.
|
|
||||||
///
|
|
||||||
/// `Clone` does not clone the different pool of searcher. `IndexReader`
|
|
||||||
/// just wraps and `Arc`.
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct MetaFileIndexReader {
|
|
||||||
inner: Arc<MetaFileIndexReaderInner>,
|
|
||||||
watch_handle_opt: Option<WatchHandle>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MetaFileIndexReader {
|
|
||||||
pub fn reload(&self) -> crate::Result<()> {
|
|
||||||
self.inner.reload()
|
|
||||||
}
|
|
||||||
pub fn searcher(&self) -> LeasedItem<Searcher> {
|
|
||||||
self.inner.searcher()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +1,15 @@
|
|||||||
mod index_writer_reader;
|
|
||||||
mod meta_file_reader;
|
|
||||||
mod pool;
|
mod pool;
|
||||||
|
|
||||||
use self::meta_file_reader::MetaFileIndexReader;
|
|
||||||
pub use self::meta_file_reader::{IndexReaderBuilder, ReloadPolicy};
|
|
||||||
pub use self::pool::LeasedItem;
|
pub use self::pool::LeasedItem;
|
||||||
|
use self::pool::Pool;
|
||||||
|
use crate::core::Segment;
|
||||||
|
use crate::directory::Directory;
|
||||||
|
use crate::directory::WatchHandle;
|
||||||
|
use crate::directory::META_LOCK;
|
||||||
pub(crate) use crate::reader::index_writer_reader::NRTReader;
|
use crate::Index;
|
||||||
|
|
||||||
use crate::Searcher;
|
use crate::Searcher;
|
||||||
|
use crate::SegmentReader;
|
||||||
/*
|
use std::sync::Arc;
|
||||||
//
|
|
||||||
//enum SegmentSource {
|
|
||||||
// FromMetaFile,
|
|
||||||
// FromWriter(Arc<RwLock<SegmentRegisters>>),
|
|
||||||
//}
|
|
||||||
//
|
|
||||||
//impl SegmentSource {
|
|
||||||
// fn from_meta_file() -> SegmentSource {
|
|
||||||
//
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
//}
|
|
||||||
|
|
||||||
/// Defines when a new version of the index should be reloaded.
|
/// Defines when a new version of the index should be reloaded.
|
||||||
///
|
///
|
||||||
@@ -137,24 +120,15 @@ struct InnerIndexReader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl InnerIndexReader {
|
impl InnerIndexReader {
|
||||||
fn load_segment_readers(&self) -> crate::Result<Vec<SegmentReader>> {
|
|
||||||
// We keep the lock until we have effectively finished opening the
|
|
||||||
// the `SegmentReader` because it prevents a diffferent process
|
|
||||||
// to garbage collect these file while we open them.
|
|
||||||
//
|
|
||||||
// Once opened, on linux & mac, the mmap will remain valid after
|
|
||||||
// the file has been deleted
|
|
||||||
// On windows, the file deletion will fail.
|
|
||||||
let _meta_lock = self.index.directory().acquire_lock(&META_LOCK)?;
|
|
||||||
let searchable_segments = self.searchable_segments()?;
|
|
||||||
searchable_segments
|
|
||||||
.iter()
|
|
||||||
.map(SegmentReader::open)
|
|
||||||
.collect::<crate::Result<_>>()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reload(&self) -> crate::Result<()> {
|
fn reload(&self) -> crate::Result<()> {
|
||||||
let segment_readers: Vec<SegmentReader> = self.load_segment_readers()?;
|
let segment_readers: Vec<SegmentReader> = {
|
||||||
|
let _meta_lock = self.index.directory().acquire_lock(&META_LOCK)?;
|
||||||
|
let searchable_segments = self.searchable_segments()?;
|
||||||
|
searchable_segments
|
||||||
|
.iter()
|
||||||
|
.map(SegmentReader::open)
|
||||||
|
.collect::<crate::Result<_>>()?
|
||||||
|
};
|
||||||
let schema = self.index.schema();
|
let schema = self.index.schema();
|
||||||
let searchers = (0..self.num_searchers)
|
let searchers = (0..self.num_searchers)
|
||||||
.map(|_| Searcher::new(schema.clone(), self.index.clone(), segment_readers.clone()))
|
.map(|_| Searcher::new(schema.clone(), self.index.clone(), segment_readers.clone()))
|
||||||
@@ -180,16 +154,18 @@ impl InnerIndexReader {
|
|||||||
///
|
///
|
||||||
/// `Clone` does not clone the different pool of searcher. `IndexReader`
|
/// `Clone` does not clone the different pool of searcher. `IndexReader`
|
||||||
/// just wraps and `Arc`.
|
/// just wraps and `Arc`.
|
||||||
=======
|
|
||||||
>>>>>>> Added NRTReader
|
|
||||||
*/
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum IndexReader {
|
pub struct IndexReader {
|
||||||
FromMetaFile(MetaFileIndexReader),
|
inner: Arc<InnerIndexReader>,
|
||||||
NRT(NRTReader),
|
watch_handle_opt: Option<WatchHandle>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl IndexReader {
|
impl IndexReader {
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn index(&self) -> Index {
|
||||||
|
self.inner.index.clone()
|
||||||
|
}
|
||||||
|
|
||||||
/// Update searchers so that they reflect the state of the last
|
/// Update searchers so that they reflect the state of the last
|
||||||
/// `.commit()`.
|
/// `.commit()`.
|
||||||
///
|
///
|
||||||
@@ -200,10 +176,7 @@ impl IndexReader {
|
|||||||
/// This automatic reload can take 10s of milliseconds to kick in however, and in unit tests
|
/// This automatic reload can take 10s of milliseconds to kick in however, and in unit tests
|
||||||
/// it can be nice to deterministically force the reload of searchers.
|
/// it can be nice to deterministically force the reload of searchers.
|
||||||
pub fn reload(&self) -> crate::Result<()> {
|
pub fn reload(&self) -> crate::Result<()> {
|
||||||
match self {
|
self.inner.reload()
|
||||||
IndexReader::FromMetaFile(meta_file_reader) => meta_file_reader.reload(),
|
|
||||||
IndexReader::NRT(nrt_reader) => nrt_reader.reload(),
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns a searcher
|
/// Returns a searcher
|
||||||
@@ -217,21 +190,6 @@ impl IndexReader {
|
|||||||
/// The same searcher must be used for a given query, as it ensures
|
/// The same searcher must be used for a given query, as it ensures
|
||||||
/// the use of a consistent segment set.
|
/// the use of a consistent segment set.
|
||||||
pub fn searcher(&self) -> LeasedItem<Searcher> {
|
pub fn searcher(&self) -> LeasedItem<Searcher> {
|
||||||
match self {
|
self.inner.searcher()
|
||||||
IndexReader::FromMetaFile(meta_file_reader) => meta_file_reader.searcher(),
|
|
||||||
IndexReader::NRT(nrt_reader) => nrt_reader.searcher(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<MetaFileIndexReader> for IndexReader {
|
|
||||||
fn from(meta_file_reader: MetaFileIndexReader) -> Self {
|
|
||||||
IndexReader::FromMetaFile(meta_file_reader)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<NRTReader> for IndexReader {
|
|
||||||
fn from(nrt_reader: NRTReader) -> Self {
|
|
||||||
IndexReader::NRT(nrt_reader)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,8 +3,9 @@ use crate::common::BinarySerializable;
|
|||||||
use crate::common::VInt;
|
use crate::common::VInt;
|
||||||
use crate::tokenizer::PreTokenizedString;
|
use crate::tokenizer::PreTokenizedString;
|
||||||
use crate::DateTime;
|
use crate::DateTime;
|
||||||
use itertools::Itertools;
|
use serde;
|
||||||
use std::io::{self, Read, Write};
|
use std::io::{self, Read, Write};
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
/// Tantivy's Document is the object that can
|
/// Tantivy's Document is the object that can
|
||||||
/// be indexed and then searched for.
|
/// be indexed and then searched for.
|
||||||
@@ -16,7 +17,7 @@ use std::io::{self, Read, Write};
|
|||||||
|
|
||||||
/// Documents are really just a list of couple `(field, value)`.
|
/// Documents are really just a list of couple `(field, value)`.
|
||||||
/// In this list, one field may appear more than once.
|
/// In this list, one field may appear more than once.
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize, Default)]
|
||||||
pub struct Document {
|
pub struct Document {
|
||||||
field_values: Vec<FieldValue>,
|
field_values: Vec<FieldValue>,
|
||||||
}
|
}
|
||||||
@@ -131,12 +132,34 @@ impl Document {
|
|||||||
pub fn get_sorted_field_values(&self) -> Vec<(Field, Vec<&FieldValue>)> {
|
pub fn get_sorted_field_values(&self) -> Vec<(Field, Vec<&FieldValue>)> {
|
||||||
let mut field_values: Vec<&FieldValue> = self.field_values().iter().collect();
|
let mut field_values: Vec<&FieldValue> = self.field_values().iter().collect();
|
||||||
field_values.sort_by_key(|field_value| field_value.field());
|
field_values.sort_by_key(|field_value| field_value.field());
|
||||||
field_values
|
|
||||||
.into_iter()
|
let mut grouped_field_values = vec![];
|
||||||
.group_by(|field_value| field_value.field())
|
|
||||||
.into_iter()
|
let mut current_field;
|
||||||
.map(|(key, group)| (key, group.collect()))
|
let mut current_group;
|
||||||
.collect::<Vec<(Field, Vec<&FieldValue>)>>()
|
|
||||||
|
let mut field_values_it = field_values.into_iter();
|
||||||
|
if let Some(field_value) = field_values_it.next() {
|
||||||
|
current_field = field_value.field();
|
||||||
|
current_group = vec![field_value]
|
||||||
|
} else {
|
||||||
|
return grouped_field_values;
|
||||||
|
}
|
||||||
|
|
||||||
|
for field_value in field_values_it {
|
||||||
|
if field_value.field() == current_field {
|
||||||
|
current_group.push(field_value);
|
||||||
|
} else {
|
||||||
|
grouped_field_values.push((
|
||||||
|
current_field,
|
||||||
|
mem::replace(&mut current_group, vec![field_value]),
|
||||||
|
));
|
||||||
|
current_field = field_value.field();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
grouped_field_values.push((current_field, current_group));
|
||||||
|
grouped_field_values
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns all of the `FieldValue`s associated the given field
|
/// Returns all of the `FieldValue`s associated the given field
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
use crate::common::BinarySerializable;
|
use crate::common::BinarySerializable;
|
||||||
|
use serde;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
/// `Field` is represented by an unsigned 32-bit integer type
|
/// `Field` is represented by an unsigned 32-bit integer type
|
||||||
/// The schema holds the mapping between field names and `Field` objects.
|
/// The schema holds the mapping between field names and `Field` objects.
|
||||||
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, Serialize, Deserialize)]
|
#[derive(
|
||||||
|
Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash, serde::Serialize, serde::Deserialize,
|
||||||
|
)]
|
||||||
pub struct Field(u32);
|
pub struct Field(u32);
|
||||||
|
|
||||||
impl Field {
|
impl Field {
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
use crate::common::BinarySerializable;
|
use crate::common::BinarySerializable;
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
use crate::schema::Value;
|
use crate::schema::Value;
|
||||||
|
use serde;
|
||||||
use std::io;
|
use std::io;
|
||||||
use std::io::Read;
|
use std::io::Read;
|
||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
/// `FieldValue` holds together a `Field` and its `Value`.
|
/// `FieldValue` holds together a `Field` and its `Value`.
|
||||||
#[derive(Debug, Clone, Ord, PartialEq, Eq, PartialOrd, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Ord, PartialEq, Eq, PartialOrd, serde::Serialize, serde::Deserialize)]
|
||||||
pub struct FieldValue {
|
pub struct FieldValue {
|
||||||
field: Field,
|
field: Field,
|
||||||
value: Value,
|
value: Value,
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// `IndexRecordOption` describes an amount information associated
|
/// `IndexRecordOption` describes an amount information associated
|
||||||
/// to a given indexed field.
|
/// to a given indexed field.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::schema::flags::{FastFlag, IndexedFlag, SchemaFlagList, StoredFlag};
|
use crate::schema::flags::{FastFlag, IndexedFlag, SchemaFlagList, StoredFlag};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
/// Express whether a field is single-value or multi-valued.
|
/// Express whether a field is single-value or multi-valued.
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::schema::Value;
|
use crate::schema::Value;
|
||||||
|
use serde::Serialize;
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
/// Internal representation of a document used for JSON
|
/// Internal representation of a document used for JSON
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use crate::schema::flags::SchemaFlagList;
|
use crate::schema::flags::SchemaFlagList;
|
||||||
use crate::schema::flags::StoredFlag;
|
use crate::schema::flags::StoredFlag;
|
||||||
use crate::schema::IndexRecordOption;
|
use crate::schema::IndexRecordOption;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::ops::BitOr;
|
use std::ops::BitOr;
|
||||||
|
|
||||||
@@ -155,30 +156,17 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_field_options() {
|
fn test_field_options() {
|
||||||
{
|
let field_options = STORED | TEXT;
|
||||||
let field_options = STORED | TEXT;
|
assert!(field_options.is_stored());
|
||||||
assert!(field_options.is_stored());
|
assert!(field_options.get_indexing_options().is_some());
|
||||||
assert!(field_options.get_indexing_options().is_some());
|
let mut schema_builder = Schema::builder();
|
||||||
}
|
schema_builder.add_text_field("body", TEXT);
|
||||||
{
|
let schema = schema_builder.build();
|
||||||
let mut schema_builder = Schema::builder();
|
let field = schema.get_field("body").unwrap();
|
||||||
schema_builder.add_text_field("body", TEXT);
|
let field_entry = schema.get_field_entry(field);
|
||||||
let schema = schema_builder.build();
|
assert!(matches!(field_entry.field_type(),
|
||||||
let field = schema.get_field("body").unwrap();
|
&FieldType::Str(ref text_options)
|
||||||
let field_entry = schema.get_field_entry(field);
|
if text_options.get_indexing_options().unwrap().tokenizer() == "default"));
|
||||||
match field_entry.field_type() {
|
|
||||||
&FieldType::Str(ref text_options) => {
|
|
||||||
assert!(text_options.get_indexing_options().is_some());
|
|
||||||
assert_eq!(
|
|
||||||
text_options.get_indexing_options().unwrap().tokenizer(),
|
|
||||||
"default"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
panic!("");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ under-count actual resultant space usage by up to 4095 bytes per file.
|
|||||||
|
|
||||||
use crate::schema::Field;
|
use crate::schema::Field;
|
||||||
use crate::SegmentComponent;
|
use crate::SegmentComponent;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
/// Indicates space usage in bytes
|
/// Indicates space usage in bytes
|
||||||
|
|||||||
@@ -435,6 +435,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_automaton_search() {
|
fn test_automaton_search() {
|
||||||
use levenshtein_automata::LevenshteinAutomatonBuilder;
|
use levenshtein_automata::LevenshteinAutomatonBuilder;
|
||||||
|
use crate::query::DFAWrapper;
|
||||||
|
|
||||||
const COUNTRIES: [&'static str; 7] = [
|
const COUNTRIES: [&'static str; 7] = [
|
||||||
"San Marino",
|
"San Marino",
|
||||||
@@ -463,7 +464,7 @@ mod tests {
|
|||||||
|
|
||||||
// We can now build an entire dfa.
|
// We can now build an entire dfa.
|
||||||
let lev_automaton_builder = LevenshteinAutomatonBuilder::new(2, true);
|
let lev_automaton_builder = LevenshteinAutomatonBuilder::new(2, true);
|
||||||
let automaton = lev_automaton_builder.build_dfa("Spaen");
|
let automaton = DFAWrapper(lev_automaton_builder.build_dfa("Spaen"));
|
||||||
|
|
||||||
let mut range = term_dict.search(automaton).into_stream();
|
let mut range = term_dict.search(automaton).into_stream();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use super::{Token, TokenFilter, TokenStream};
|
use super::{Token, TokenFilter, TokenStream};
|
||||||
use crate::tokenizer::BoxTokenStream;
|
use crate::tokenizer::BoxTokenStream;
|
||||||
use rust_stemmers::{self, Algorithm};
|
use rust_stemmers::{self, Algorithm};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// Available stemmer languages.
|
/// Available stemmer languages.
|
||||||
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Copy, Clone)]
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::tokenizer::{BoxTokenStream, Token, TokenStream, TokenStreamChain};
|
use crate::tokenizer::{BoxTokenStream, Token, TokenStream, TokenStreamChain};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
use std::cmp::Ordering;
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
/// Struct representing pre-tokenized text
|
/// Struct representing pre-tokenized text
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use crate::tokenizer::TokenStreamChain;
|
use crate::tokenizer::TokenStreamChain;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
/// The tokenizer module contains all of the tools used to process
|
/// The tokenizer module contains all of the tools used to process
|
||||||
/// text in `tantivy`.
|
/// text in `tantivy`.
|
||||||
use std::borrow::{Borrow, BorrowMut};
|
use std::borrow::{Borrow, BorrowMut};
|
||||||
|
|||||||
Reference in New Issue
Block a user