2018-12-30 20:04:02 +08:00
|
|
|
#![cfg_attr(feature = "nightly", feature(test))]
|
|
|
|
|
2018-11-28 02:11:33 +08:00
|
|
|
pub mod automaton;
|
2018-12-02 23:45:17 +08:00
|
|
|
pub mod database;
|
2018-11-08 19:05:59 +08:00
|
|
|
pub mod data;
|
2018-05-27 21:23:43 +08:00
|
|
|
pub mod rank;
|
2018-09-27 22:32:17 +08:00
|
|
|
pub mod tokenizer;
|
2018-10-10 00:23:35 +08:00
|
|
|
mod common_words;
|
2018-07-11 03:29:17 +08:00
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
use std::fmt;
|
|
|
|
|
2018-12-13 18:52:34 +08:00
|
|
|
pub use rocksdb;
|
|
|
|
|
2018-09-27 22:59:41 +08:00
|
|
|
pub use self::tokenizer::Tokenizer;
|
2018-10-10 00:23:35 +08:00
|
|
|
pub use self::common_words::CommonWords;
|
2018-05-13 21:12:15 +08:00
|
|
|
|
2018-12-22 19:00:24 +08:00
|
|
|
/// Represent an internally generated document unique identifier.
|
|
|
|
///
|
|
|
|
/// It is used to inform the database the document you want to deserialize.
|
|
|
|
/// Helpful for custom ranking.
|
|
|
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, PartialOrd, Ord, Hash)]
|
2018-12-25 19:26:38 +08:00
|
|
|
pub struct DocumentId(u64);
|
2018-05-27 21:23:43 +08:00
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// Represent an attribute number along with the word index
|
|
|
|
/// according to the tokenizer used.
|
|
|
|
///
|
|
|
|
/// It can accept up to 1024 attributes and word positions
|
|
|
|
/// can be maximum 2^22.
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct Attribute(u32);
|
|
|
|
|
|
|
|
impl Attribute {
|
2018-12-28 23:15:22 +08:00
|
|
|
/// Construct an `Attribute` from an attribute number and
|
|
|
|
/// the word position of a match according to the tokenizer used.
|
|
|
|
fn new(attribute: u16, index: u32) -> Result<Attribute, AttributeError> {
|
|
|
|
if attribute & 0b1111_1100_0000_0000 != 0 {
|
|
|
|
return Err(AttributeError::AttributeTooBig)
|
|
|
|
}
|
|
|
|
|
|
|
|
if index & 0b1111_1111_1100_0000_0000_0000_0000 != 0 {
|
|
|
|
return Err(AttributeError::IndexTooBig)
|
|
|
|
}
|
|
|
|
|
2019-01-01 06:20:18 +08:00
|
|
|
let attribute = u32::from(attribute) << 22;
|
2018-12-28 23:15:22 +08:00
|
|
|
Ok(Attribute(attribute | index))
|
|
|
|
}
|
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// Construct an `Attribute` from an attribute number and
|
|
|
|
/// the word position of a match according to the tokenizer used.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// The attribute must not be greater than 1024
|
|
|
|
/// and the word index not greater than 2^22.
|
2018-12-28 23:15:22 +08:00
|
|
|
fn new_faillible(attribute: u16, index: u32) -> Attribute {
|
|
|
|
match Attribute::new(attribute, index) {
|
|
|
|
Ok(attribute) => attribute,
|
|
|
|
Err(AttributeError::AttributeTooBig) => {
|
|
|
|
panic!("attribute must not be greater than 1024")
|
|
|
|
},
|
|
|
|
Err(AttributeError::IndexTooBig) => {
|
|
|
|
panic!("attribute word index must not be greater than 2^22")
|
|
|
|
},
|
|
|
|
}
|
2018-12-23 23:46:49 +08:00
|
|
|
}
|
|
|
|
|
2018-12-29 19:26:33 +08:00
|
|
|
#[inline]
|
2019-01-01 06:20:18 +08:00
|
|
|
pub fn attribute(self) -> u16 {
|
2018-12-23 23:46:49 +08:00
|
|
|
(self.0 >> 22) as u16
|
|
|
|
}
|
|
|
|
|
2018-12-29 19:26:33 +08:00
|
|
|
#[inline]
|
2019-01-01 06:20:18 +08:00
|
|
|
pub fn word_index(self) -> u32 {
|
2018-12-23 23:46:49 +08:00
|
|
|
self.0 & 0b0000_0000_0011_1111_1111_1111_1111
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for Attribute {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("Attribute")
|
|
|
|
.field("attribute", &self.attribute())
|
|
|
|
.field("word_index", &self.word_index())
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
enum AttributeError {
|
|
|
|
AttributeTooBig,
|
|
|
|
IndexTooBig,
|
|
|
|
}
|
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// Represent a word position in bytes along with the length of it.
|
|
|
|
///
|
|
|
|
/// It can represent words byte index to maximum 2^22 and
|
|
|
|
/// up to words of length 1024.
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct WordArea(u32);
|
|
|
|
|
|
|
|
impl WordArea {
|
|
|
|
/// Construct a `WordArea` from a word position in bytes
|
|
|
|
/// and the length of it.
|
|
|
|
///
|
|
|
|
/// # Panics
|
|
|
|
///
|
|
|
|
/// The byte index must not be greater than 2^22
|
|
|
|
/// and the length not greater than 1024.
|
2018-12-28 23:15:22 +08:00
|
|
|
fn new(byte_index: u32, length: u16) -> Result<WordArea, WordAreaError> {
|
|
|
|
if byte_index & 0b1111_1111_1100_0000_0000_0000_0000 != 0 {
|
|
|
|
return Err(WordAreaError::ByteIndexTooBig)
|
|
|
|
}
|
|
|
|
|
|
|
|
if length & 0b1111_1100_0000_0000 != 0 {
|
|
|
|
return Err(WordAreaError::LengthTooBig)
|
|
|
|
}
|
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
let byte_index = byte_index << 10;
|
2019-01-01 06:20:18 +08:00
|
|
|
Ok(WordArea(byte_index | u32::from(length)))
|
2018-12-28 23:15:22 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
fn new_faillible(byte_index: u32, length: u16) -> WordArea {
|
|
|
|
match WordArea::new(byte_index, length) {
|
|
|
|
Ok(word_area) => word_area,
|
|
|
|
Err(WordAreaError::ByteIndexTooBig) => {
|
|
|
|
panic!("word area byte index must not be greater than 2^22")
|
|
|
|
},
|
|
|
|
Err(WordAreaError::LengthTooBig) => {
|
|
|
|
panic!("word area length must not be greater than 1024")
|
|
|
|
},
|
|
|
|
}
|
2018-12-23 23:46:49 +08:00
|
|
|
}
|
|
|
|
|
2018-12-29 19:26:33 +08:00
|
|
|
#[inline]
|
2019-01-01 06:20:18 +08:00
|
|
|
pub fn byte_index(self) -> u32 {
|
2018-12-23 23:46:49 +08:00
|
|
|
self.0 >> 10
|
|
|
|
}
|
|
|
|
|
2018-12-29 19:26:33 +08:00
|
|
|
#[inline]
|
2019-01-01 06:20:18 +08:00
|
|
|
pub fn length(self) -> u16 {
|
2018-12-23 23:46:49 +08:00
|
|
|
(self.0 & 0b0000_0000_0000_0000_0011_1111_1111) as u16
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for WordArea {
|
|
|
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
|
|
|
f.debug_struct("WordArea")
|
|
|
|
.field("byte_index", &self.byte_index())
|
|
|
|
.field("length", &self.length())
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
enum WordAreaError {
|
|
|
|
ByteIndexTooBig,
|
|
|
|
LengthTooBig,
|
|
|
|
}
|
|
|
|
|
2018-05-27 17:15:05 +08:00
|
|
|
/// This structure represent the position of a word
|
|
|
|
/// in a document and its attributes.
|
|
|
|
///
|
|
|
|
/// This is stored in the map, generated at index time,
|
|
|
|
/// extracted and interpreted at search time.
|
2018-12-23 23:46:49 +08:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
2018-07-11 03:29:17 +08:00
|
|
|
#[repr(C)]
|
2018-05-27 17:15:05 +08:00
|
|
|
pub struct DocIndex {
|
|
|
|
/// The document identifier where the word was found.
|
2018-10-17 19:35:34 +08:00
|
|
|
pub document_id: DocumentId,
|
2018-05-27 17:15:05 +08:00
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// The attribute in the document where the word was found
|
|
|
|
/// along with the index in it.
|
|
|
|
pub attribute: Attribute,
|
2018-05-27 17:15:05 +08:00
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// The position in bytes where the word was found
|
|
|
|
/// along with the length of it.
|
2018-05-27 17:15:05 +08:00
|
|
|
///
|
2018-12-23 23:46:49 +08:00
|
|
|
/// It informs on the original word area in the text indexed
|
|
|
|
/// without needing to run the tokenizer again.
|
|
|
|
pub word_area: WordArea,
|
2018-05-13 21:12:15 +08:00
|
|
|
}
|
|
|
|
|
2018-05-27 17:15:05 +08:00
|
|
|
/// This structure represent a matching word with informations
|
|
|
|
/// on the location of the word in the document.
|
|
|
|
///
|
|
|
|
/// The order of the field is important because it defines
|
|
|
|
/// the way these structures are ordered between themselves.
|
|
|
|
///
|
|
|
|
/// The word in itself is not important.
|
2018-05-27 21:23:43 +08:00
|
|
|
// TODO do data oriented programming ? very arrays ?
|
2018-12-23 23:46:49 +08:00
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
2018-05-27 17:15:05 +08:00
|
|
|
pub struct Match {
|
2018-05-27 21:23:43 +08:00
|
|
|
/// The word index in the query sentence.
|
|
|
|
/// Same as the `attribute_index` but for the query words.
|
|
|
|
///
|
|
|
|
/// Used to retrieve the automaton that match this word.
|
|
|
|
pub query_index: u32,
|
|
|
|
|
2018-05-27 17:15:05 +08:00
|
|
|
/// The distance the word has with the query word
|
|
|
|
/// (i.e. the Levenshtein distance).
|
|
|
|
pub distance: u8,
|
|
|
|
|
2018-12-23 23:46:49 +08:00
|
|
|
/// The attribute in the document where the word was found
|
|
|
|
/// along with the index in it.
|
|
|
|
pub attribute: Attribute,
|
2018-07-07 02:58:06 +08:00
|
|
|
|
|
|
|
/// Whether the word that match is an exact match or a prefix.
|
|
|
|
pub is_exact: bool,
|
2018-12-23 23:46:49 +08:00
|
|
|
|
|
|
|
/// The position in bytes where the word was found
|
|
|
|
/// along with the length of it.
|
|
|
|
///
|
|
|
|
/// It informs on the original word area in the text indexed
|
|
|
|
/// without needing to run the tokenizer again.
|
|
|
|
pub word_area: WordArea,
|
2018-05-13 21:12:15 +08:00
|
|
|
}
|
2018-05-27 21:23:43 +08:00
|
|
|
|
|
|
|
impl Match {
|
|
|
|
pub fn zero() -> Self {
|
|
|
|
Match {
|
|
|
|
query_index: 0,
|
|
|
|
distance: 0,
|
2018-12-28 23:15:22 +08:00
|
|
|
attribute: Attribute::new_faillible(0, 0),
|
2018-07-07 02:58:06 +08:00
|
|
|
is_exact: false,
|
2018-12-28 23:15:22 +08:00
|
|
|
word_area: WordArea::new_faillible(0, 0),
|
2018-05-27 21:23:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn max() -> Self {
|
|
|
|
Match {
|
|
|
|
query_index: u32::max_value(),
|
|
|
|
distance: u8::max_value(),
|
2018-12-23 23:46:49 +08:00
|
|
|
attribute: Attribute(u32::max_value()),
|
2018-07-07 02:58:06 +08:00
|
|
|
is_exact: true,
|
2018-12-23 23:46:49 +08:00
|
|
|
word_area: WordArea(u32::max_value()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
use quickcheck::{quickcheck, TestResult};
|
|
|
|
use std::mem;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn docindex_mem_size() {
|
|
|
|
assert_eq!(mem::size_of::<DocIndex>(), 16);
|
|
|
|
}
|
|
|
|
|
|
|
|
quickcheck! {
|
|
|
|
fn qc_attribute(gen_attr: u16, gen_index: u32) -> TestResult {
|
|
|
|
if gen_attr > 2_u16.pow(10) || gen_index > 2_u32.pow(22) {
|
|
|
|
return TestResult::discard()
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
let attribute = Attribute::new_faillible(gen_attr, gen_index);
|
2018-12-23 23:46:49 +08:00
|
|
|
|
|
|
|
let valid_attribute = attribute.attribute() == gen_attr;
|
|
|
|
let valid_index = attribute.word_index() == gen_index;
|
|
|
|
|
|
|
|
TestResult::from_bool(valid_attribute && valid_index)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn qc_attribute_ord(gen_attr: u16, gen_index: u32) -> TestResult {
|
|
|
|
if gen_attr >= 2_u16.pow(10) || gen_index >= 2_u32.pow(22) {
|
|
|
|
return TestResult::discard()
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
let a = Attribute::new_faillible(gen_attr, gen_index);
|
|
|
|
let b = Attribute::new_faillible(gen_attr + 1, gen_index + 1);
|
2018-12-23 23:46:49 +08:00
|
|
|
|
|
|
|
TestResult::from_bool(a < b)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn qc_word_area(gen_byte_index: u32, gen_length: u16) -> TestResult {
|
|
|
|
if gen_byte_index > 2_u32.pow(22) || gen_length > 2_u16.pow(10) {
|
|
|
|
return TestResult::discard()
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
let word_area = WordArea::new_faillible(gen_byte_index, gen_length);
|
2018-12-23 23:46:49 +08:00
|
|
|
|
|
|
|
let valid_char_index = word_area.byte_index() == gen_byte_index;
|
|
|
|
let valid_length = word_area.length() == gen_length;
|
|
|
|
|
|
|
|
TestResult::from_bool(valid_char_index && valid_length)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn qc_word_area_ord(gen_byte_index: u32, gen_length: u16) -> TestResult {
|
|
|
|
if gen_byte_index >= 2_u32.pow(22) || gen_length >= 2_u16.pow(10) {
|
|
|
|
return TestResult::discard()
|
|
|
|
}
|
|
|
|
|
2018-12-28 23:15:22 +08:00
|
|
|
let a = WordArea::new_faillible(gen_byte_index, gen_length);
|
|
|
|
let b = WordArea::new_faillible(gen_byte_index + 1, gen_length + 1);
|
2018-12-23 23:46:49 +08:00
|
|
|
|
|
|
|
TestResult::from_bool(a < b)
|
2018-05-27 21:23:43 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|