mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-01-06 17:22:54 +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,3 +1,7 @@
|
|||||||
|
Tantivy 0.13.0
|
||||||
|
======================
|
||||||
|
- Bugfix in `FuzzyTermQuery` not matching terms by prefix when it should (@Peachball)
|
||||||
|
|
||||||
Tantivy 0.12.0
|
Tantivy 0.12.0
|
||||||
======================
|
======================
|
||||||
- Removing static dispatch in tokenizers for simplicity. (#762)
|
- Removing static dispatch in tokenizers for simplicity. (#762)
|
||||||
|
|||||||
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)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use super::segment::create_segment;
|
|
||||||
use super::segment::Segment;
|
use super::segment::Segment;
|
||||||
use crate::core::Executor;
|
use crate::core::Executor;
|
||||||
use crate::core::IndexMeta;
|
use crate::core::IndexMeta;
|
||||||
@@ -337,7 +336,7 @@ impl Index {
|
|||||||
|
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
pub fn segment(&self, segment_meta: SegmentMeta) -> Segment {
|
pub fn segment(&self, segment_meta: SegmentMeta) -> Segment {
|
||||||
create_segment(self.clone(), segment_meta)
|
Segment::for_index(self.clone(), segment_meta)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new segment.
|
/// Creates a new segment.
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -24,15 +24,12 @@ impl fmt::Debug for Segment {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new segment given an `Index` and a `SegmentId`
|
|
||||||
///
|
|
||||||
/// The function is here to make it private outside `tantivy`.
|
|
||||||
/// #[doc(hidden)]
|
|
||||||
pub fn create_segment(index: Index, meta: SegmentMeta) -> Segment {
|
|
||||||
Segment { index, meta }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Segment {
|
impl Segment {
|
||||||
|
/// Creates a new segment given an `Index` and a `SegmentId`
|
||||||
|
pub(crate) fn for_index(index: Index, meta: SegmentMeta) -> Segment {
|
||||||
|
Segment { index, meta }
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the index the segment belongs to.
|
/// Returns the index the segment belongs to.
|
||||||
pub fn index(&self) -> &Index {
|
pub fn index(&self) -> &Index {
|
||||||
&self.index
|
&self.index
|
||||||
|
|||||||
@@ -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)]
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -144,6 +144,22 @@ impl RAMDirectory {
|
|||||||
pub fn total_mem_usage(&self) -> usize {
|
pub fn total_mem_usage(&self) -> usize {
|
||||||
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<()> {
|
||||||
|
let wlock = self.fs.write().unwrap();
|
||||||
|
for (path, source) in wlock.fs.iter() {
|
||||||
|
let mut dest_wrt = dest.open_write(path)?;
|
||||||
|
dest_wrt.write_all(source.as_slice())?;
|
||||||
|
dest_wrt.terminate()?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Directory for RAMDirectory {
|
impl Directory for RAMDirectory {
|
||||||
@@ -204,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 {
|
||||||
|
|||||||
@@ -155,6 +155,8 @@ pub(crate) fn advance_deletes(
|
|||||||
None => BitSet::with_max_value(max_doc),
|
None => BitSet::with_max_value(max_doc),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
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,
|
||||||
@@ -164,6 +166,8 @@ pub(crate) fn advance_deletes(
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
// TODO optimize
|
// TODO optimize
|
||||||
|
// 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) {
|
||||||
@@ -172,8 +176,9 @@ pub(crate) fn advance_deletes(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let num_deleted_docs = delete_bitset.len();
|
let num_deleted_docs: u32 = delete_bitset.len() as u32;
|
||||||
if num_deleted_docs > 0 {
|
if num_deleted_docs > num_deleted_docs_before {
|
||||||
|
// There are new deletes. We need to write a new delete file.
|
||||||
segment = segment.with_delete_meta(num_deleted_docs as u32, target_opstamp);
|
segment = segment.with_delete_meta(num_deleted_docs as u32, target_opstamp);
|
||||||
let mut delete_file = segment.open_write(SegmentComponent::DELETE)?;
|
let mut delete_file = segment.open_write(SegmentComponent::DELETE)?;
|
||||||
write_delete_bitset(&delete_bitset, max_doc, &mut delete_file)?;
|
write_delete_bitset(&delete_bitset, max_doc, &mut delete_file)?;
|
||||||
@@ -803,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")`
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
142
src/lib.rs
142
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;
|
||||||
|
|
||||||
@@ -173,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;
|
||||||
@@ -271,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
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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