mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2025-12-29 21:42:55 +00:00
Compare commits
20 Commits
typed-colu
...
use_column
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
375d1f9dac | ||
|
|
2874554ee4 | ||
|
|
cbc70a9eae | ||
|
|
226d0f88bc | ||
|
|
9548570e88 | ||
|
|
9a296b29b7 | ||
|
|
b31fd389d8 | ||
|
|
89cec79813 | ||
|
|
d09d91a856 | ||
|
|
50d8a8bc32 | ||
|
|
08919a2900 | ||
|
|
8ba333f1b4 | ||
|
|
a2ca12995e | ||
|
|
e3d504d833 | ||
|
|
5a42c5aae9 | ||
|
|
a86b104a40 | ||
|
|
f9abd256b7 | ||
|
|
9f42b6440a | ||
|
|
c723ed3f0b | ||
|
|
d72ea7d353 |
@@ -55,6 +55,7 @@ measure_time = "0.8.2"
|
||||
async-trait = "0.1.53"
|
||||
arc-swap = "1.5.0"
|
||||
|
||||
columnar = { version="0.1", path="./columnar", package ="tantivy-columnar" }
|
||||
sstable = { version="0.1", path="./sstable", package ="tantivy-sstable", optional = true }
|
||||
stacker = { version="0.1", path="./stacker", package ="tantivy-stacker" }
|
||||
tantivy-query-grammar = { version= "0.19.0", path="./query-grammar" }
|
||||
@@ -107,7 +108,7 @@ unstable = [] # useful for benches.
|
||||
quickwit = ["sstable"]
|
||||
|
||||
[workspace]
|
||||
members = ["query-grammar", "bitpacker", "common", "fastfield_codecs", "ownedbytes", "stacker", "sstable", "tokenizer-api"]
|
||||
members = ["query-grammar", "bitpacker", "common", "fastfield_codecs", "ownedbytes", "stacker", "sstable", "tokenizer-api", "columnar"]
|
||||
|
||||
# Following the "fail" crate best practises, we isolate
|
||||
# tests that define specific behavior in fail check points
|
||||
|
||||
45
README.md
45
README.md
@@ -41,7 +41,7 @@ Your mileage WILL vary depending on the nature of queries and their load.
|
||||
- SIMD integer compression when the platform/CPU includes the SSE2 instruction set
|
||||
- Single valued and multivalued u64, i64, and f64 fast fields (equivalent of doc values in Lucene)
|
||||
- `&[u8]` fast fields
|
||||
- Text, i64, u64, f64, dates, and hierarchical facet fields
|
||||
- Text, i64, u64, f64, dates, ip, bool, and hierarchical facet fields
|
||||
- Compressed document store (LZ4, Zstd, None, Brotli, Snap)
|
||||
- Range queries
|
||||
- Faceted search
|
||||
@@ -80,56 +80,21 @@ There are many ways to support this project.
|
||||
# Contributing code
|
||||
|
||||
We use the GitHub Pull Request workflow: reference a GitHub ticket and/or include a comprehensive commit message when opening a PR.
|
||||
Feel free to update CHANGELOG.md with your contribution.
|
||||
|
||||
## Tokenizer
|
||||
|
||||
When implementing a tokenizer for tantivy depend on the `tantivy-tokenizer-api` crate.
|
||||
|
||||
## Minimum supported Rust version
|
||||
|
||||
Tantivy currently requires at least Rust 1.62 or later to compile.
|
||||
|
||||
## Clone and build locally
|
||||
|
||||
Tantivy compiles on stable Rust.
|
||||
To check out and run tests, you can simply run:
|
||||
|
||||
```bash
|
||||
git clone https://github.com/quickwit-oss/tantivy.git
|
||||
cd tantivy
|
||||
cargo build
|
||||
```
|
||||
|
||||
## Run tests
|
||||
|
||||
Some tests will not run with just `cargo test` because of `fail-rs`.
|
||||
To run the tests exhaustively, run `./run-tests.sh`.
|
||||
|
||||
## Debug
|
||||
|
||||
You might find it useful to step through the programme with a debugger.
|
||||
|
||||
### A failing test
|
||||
|
||||
Make sure you haven't run `cargo clean` after the most recent `cargo test` or `cargo build` to guarantee that the `target/` directory exists. Use this bash script to find the name of the most recent debug build of Tantivy and run it under `rust-gdb`:
|
||||
|
||||
```bash
|
||||
find target/debug/ -maxdepth 1 -executable -type f -name "tantivy*" -printf '%TY-%Tm-%Td %TT %p\n' | sort -r | cut -d " " -f 3 | xargs -I RECENT_DBG_TANTIVY rust-gdb RECENT_DBG_TANTIVY
|
||||
```
|
||||
|
||||
Now that you are in `rust-gdb`, you can set breakpoints on lines and methods that match your source code and run the debug executable with flags that you normally pass to `cargo test` like this:
|
||||
|
||||
```bash
|
||||
$gdb run --test-threads 1 --test $NAME_OF_TEST
|
||||
```
|
||||
|
||||
### An example
|
||||
|
||||
By default, `rustc` compiles everything in the `examples/` directory in debug mode. This makes it easy for you to make examples to reproduce bugs:
|
||||
|
||||
```bash
|
||||
rust-gdb target/debug/examples/$EXAMPLE_NAME
|
||||
$ gdb run
|
||||
git clone https://github.com/quickwit-oss/tantivy.git
|
||||
cd tantivy
|
||||
cargo test
|
||||
```
|
||||
|
||||
# Companies Using Tantivy
|
||||
|
||||
@@ -15,3 +15,7 @@ homepage = "https://github.com/quickwit-oss/tantivy"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8"
|
||||
proptest = "1"
|
||||
|
||||
@@ -4,9 +4,39 @@ extern crate test;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use tantivy_bitpacker::BlockedBitpacker;
|
||||
use rand::seq::IteratorRandom;
|
||||
use rand::thread_rng;
|
||||
use tantivy_bitpacker::{BitPacker, BitUnpacker, BlockedBitpacker};
|
||||
use test::Bencher;
|
||||
|
||||
#[inline(never)]
|
||||
fn create_bitpacked_data(bit_width: u8, num_els: u32) -> Vec<u8> {
|
||||
let mut bitpacker = BitPacker::new();
|
||||
let mut buffer = Vec::new();
|
||||
for _ in 0..num_els {
|
||||
// the values do not matter.
|
||||
bitpacker.write(0u64, bit_width, &mut buffer).unwrap();
|
||||
bitpacker.flush(&mut buffer).unwrap();
|
||||
}
|
||||
buffer
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_bitpacking_read(b: &mut Bencher) {
|
||||
let bit_width = 3;
|
||||
let num_els = 1_000_000u32;
|
||||
let bit_unpacker = BitUnpacker::new(bit_width);
|
||||
let data = create_bitpacked_data(bit_width, num_els);
|
||||
let idxs: Vec<u32> = (0..num_els).choose_multiple(&mut thread_rng(), 100_000);
|
||||
b.iter(|| {
|
||||
let mut out = 0u64;
|
||||
for &idx in &idxs {
|
||||
out = out.wrapping_add(bit_unpacker.get(idx, &data[..]));
|
||||
}
|
||||
out
|
||||
});
|
||||
}
|
||||
|
||||
#[bench]
|
||||
fn bench_blockedbitp_read(b: &mut Bencher) {
|
||||
let mut blocked_bitpacker = BlockedBitpacker::new();
|
||||
@@ -14,9 +44,9 @@ mod tests {
|
||||
blocked_bitpacker.add(val * val);
|
||||
}
|
||||
b.iter(|| {
|
||||
let mut out = 0;
|
||||
let mut out = 0u64;
|
||||
for val in 0..=21500 {
|
||||
out = blocked_bitpacker.get(val);
|
||||
out = out.wrapping_add(blocked_bitpacker.get(val));
|
||||
}
|
||||
out
|
||||
});
|
||||
|
||||
@@ -56,27 +56,31 @@ impl BitPacker {
|
||||
|
||||
pub fn close<TWrite: io::Write>(&mut self, output: &mut TWrite) -> io::Result<()> {
|
||||
self.flush(output)?;
|
||||
// Padding the write file to simplify reads.
|
||||
output.write_all(&[0u8; 7])?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
#[derive(Clone, Debug, Default, Copy)]
|
||||
pub struct BitUnpacker {
|
||||
num_bits: u64,
|
||||
num_bits: u32,
|
||||
mask: u64,
|
||||
}
|
||||
|
||||
impl BitUnpacker {
|
||||
/// Creates a bit unpacker, that assumes the same bitwidth for all values.
|
||||
///
|
||||
/// The bitunpacker works by doing an unaligned read of 8 bytes.
|
||||
/// For this reason, values of `num_bits` between
|
||||
/// [57..63] are forbidden.
|
||||
pub fn new(num_bits: u8) -> BitUnpacker {
|
||||
assert!(num_bits <= 7 * 8 || num_bits == 64);
|
||||
let mask: u64 = if num_bits == 64 {
|
||||
!0u64
|
||||
} else {
|
||||
(1u64 << num_bits) - 1u64
|
||||
};
|
||||
BitUnpacker {
|
||||
num_bits: u64::from(num_bits),
|
||||
num_bits: u32::from(num_bits),
|
||||
mask,
|
||||
}
|
||||
}
|
||||
@@ -87,28 +91,40 @@ impl BitUnpacker {
|
||||
|
||||
#[inline]
|
||||
pub fn get(&self, idx: u32, data: &[u8]) -> u64 {
|
||||
if self.num_bits == 0 {
|
||||
return 0u64;
|
||||
}
|
||||
let addr_in_bits = idx * self.num_bits as u32;
|
||||
let addr_in_bits = idx * self.num_bits;
|
||||
let addr = (addr_in_bits >> 3) as usize;
|
||||
if addr + 8 > data.len() {
|
||||
if self.num_bits == 0 {
|
||||
return 0;
|
||||
}
|
||||
let bit_shift = addr_in_bits & 7;
|
||||
return self.get_slow_path(addr, bit_shift, data);
|
||||
}
|
||||
let bit_shift = addr_in_bits & 7;
|
||||
debug_assert!(
|
||||
addr + 8 <= data.len(),
|
||||
"The fast field field should have been padded with 7 bytes."
|
||||
);
|
||||
let bytes: [u8; 8] = (&data[addr..addr + 8]).try_into().unwrap();
|
||||
let val_unshifted_unmasked: u64 = u64::from_le_bytes(bytes);
|
||||
let val_shifted = val_unshifted_unmasked >> bit_shift;
|
||||
val_shifted & self.mask
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
fn get_slow_path(&self, addr: usize, bit_shift: u32, data: &[u8]) -> u64 {
|
||||
let mut bytes: [u8; 8] = [0u8; 8];
|
||||
let available_bytes = data.len() - addr;
|
||||
// This function is meant to only be called if we did not have 8 bytes to load.
|
||||
debug_assert!(available_bytes < 8);
|
||||
bytes[..available_bytes].copy_from_slice(&data[addr..]);
|
||||
let val_unshifted_unmasked: u64 = u64::from_le_bytes(bytes);
|
||||
let val_shifted = val_unshifted_unmasked >> bit_shift;
|
||||
val_shifted & self.mask
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::{BitPacker, BitUnpacker};
|
||||
|
||||
fn create_fastfield_bitpacker(len: usize, num_bits: u8) -> (BitUnpacker, Vec<u64>, Vec<u8>) {
|
||||
fn create_bitpacker(len: usize, num_bits: u8) -> (BitUnpacker, Vec<u64>, Vec<u8>) {
|
||||
let mut data = Vec::new();
|
||||
let mut bitpacker = BitPacker::new();
|
||||
let max_val: u64 = (1u64 << num_bits as u64) - 1u64;
|
||||
@@ -119,13 +135,13 @@ mod test {
|
||||
bitpacker.write(val, num_bits, &mut data).unwrap();
|
||||
}
|
||||
bitpacker.close(&mut data).unwrap();
|
||||
assert_eq!(data.len(), ((num_bits as usize) * len + 7) / 8 + 7);
|
||||
assert_eq!(data.len(), ((num_bits as usize) * len + 7) / 8);
|
||||
let bitunpacker = BitUnpacker::new(num_bits);
|
||||
(bitunpacker, vals, data)
|
||||
}
|
||||
|
||||
fn test_bitpacker_util(len: usize, num_bits: u8) {
|
||||
let (bitunpacker, vals, data) = create_fastfield_bitpacker(len, num_bits);
|
||||
let (bitunpacker, vals, data) = create_bitpacker(len, num_bits);
|
||||
for (i, val) in vals.iter().enumerate() {
|
||||
assert_eq!(bitunpacker.get(i as u32, &data), *val);
|
||||
}
|
||||
@@ -139,4 +155,49 @@ mod test {
|
||||
test_bitpacker_util(6, 14);
|
||||
test_bitpacker_util(1000, 14);
|
||||
}
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
fn num_bits_strategy() -> impl Strategy<Value = u8> {
|
||||
prop_oneof!(Just(0), Just(1), 2u8..56u8, Just(56), Just(64),)
|
||||
}
|
||||
|
||||
fn vals_strategy() -> impl Strategy<Value = (u8, Vec<u64>)> {
|
||||
(num_bits_strategy(), 0usize..100usize).prop_flat_map(|(num_bits, len)| {
|
||||
let max_val = if num_bits == 64 {
|
||||
u64::MAX
|
||||
} else {
|
||||
(1u64 << num_bits as u32) - 1
|
||||
};
|
||||
let vals = proptest::collection::vec(0..=max_val, len);
|
||||
vals.prop_map(move |vals| (num_bits, vals))
|
||||
})
|
||||
}
|
||||
|
||||
fn test_bitpacker_aux(num_bits: u8, vals: &[u64]) {
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
let mut bitpacker = BitPacker::new();
|
||||
for &val in vals {
|
||||
bitpacker.write(val, num_bits, &mut buffer).unwrap();
|
||||
}
|
||||
bitpacker.flush(&mut buffer).unwrap();
|
||||
assert_eq!(buffer.len(), (vals.len() * num_bits as usize + 7) / 8);
|
||||
let bitunpacker = BitUnpacker::new(num_bits);
|
||||
let max_val = if num_bits == 64 {
|
||||
u64::MAX
|
||||
} else {
|
||||
(1u64 << num_bits) - 1
|
||||
};
|
||||
for (i, val) in vals.iter().copied().enumerate() {
|
||||
assert!(val <= max_val);
|
||||
assert_eq!(bitunpacker.get(i as u32, &buffer), val);
|
||||
}
|
||||
}
|
||||
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn test_bitpacker_proptest((num_bits, vals) in vals_strategy()) {
|
||||
test_bitpacker_aux(num_bits, &vals);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,28 +5,23 @@ edition = "2021"
|
||||
license = "MIT"
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.10.5"
|
||||
log = "0.4.17"
|
||||
fnv = "1.0.7"
|
||||
fastdivide = "0.4.0"
|
||||
rand = { version = "0.8.5", optional = true }
|
||||
measure_time = { version = "0.8.2", optional = true }
|
||||
prettytable-rs = { version = "0.10.0", optional = true }
|
||||
|
||||
stacker = { path = "../stacker", package="tantivy-stacker"}
|
||||
serde_json = "1"
|
||||
thiserror = "1"
|
||||
fnv = "1"
|
||||
sstable = { path = "../sstable", package = "tantivy-sstable" }
|
||||
common = { path = "../common", package = "tantivy-common" }
|
||||
itertools = "0.10"
|
||||
log = "0.4"
|
||||
tantivy-bitpacker = { version= "0.3", path = "../bitpacker/" }
|
||||
prettytable-rs = {version="0.10.0", optional= true}
|
||||
rand = {version="0.8.3", optional= true}
|
||||
fastdivide = "0.4"
|
||||
measure_time = { version="0.8.2", optional=true}
|
||||
|
||||
[dev-dependencies]
|
||||
proptest = "1"
|
||||
more-asserts = "0.3.0"
|
||||
rand = "0.8.3"
|
||||
|
||||
# temporary
|
||||
[workspace]
|
||||
members = []
|
||||
proptest = "1.0.0"
|
||||
more-asserts = "0.3.1"
|
||||
rand = "0.8.5"
|
||||
|
||||
[features]
|
||||
unstable = []
|
||||
|
||||
@@ -5,9 +5,16 @@ use std::sync::Arc;
|
||||
use sstable::{Dictionary, VoidSSTable};
|
||||
|
||||
use crate::column::Column;
|
||||
use crate::column_index::ColumnIndex;
|
||||
use crate::RowId;
|
||||
|
||||
/// Dictionary encoded column.
|
||||
///
|
||||
/// The column simply gives access to a regular u64-column that, in
|
||||
/// which the values are term-ordinals.
|
||||
///
|
||||
/// These ordinals are ids uniquely identify the bytes that are stored in
|
||||
/// the column. These ordinals are small, and sorted in the same order
|
||||
/// as the term_ord_column.
|
||||
#[derive(Clone)]
|
||||
pub struct BytesColumn {
|
||||
pub(crate) dictionary: Arc<Dictionary<VoidSSTable>>,
|
||||
@@ -15,26 +22,57 @@ pub struct BytesColumn {
|
||||
}
|
||||
|
||||
impl BytesColumn {
|
||||
/// Fills the given `output` buffer with the term associated to the ordinal `ord`.
|
||||
///
|
||||
/// Returns `false` if the term does not exist (e.g. `term_ord` is greater or equal to the
|
||||
/// overll number of terms).
|
||||
pub fn term_ord_to_str(&self, term_ord: u64, output: &mut Vec<u8>) -> io::Result<bool> {
|
||||
self.dictionary.ord_to_term(term_ord, output)
|
||||
pub fn ord_to_bytes(&self, ord: u64, output: &mut Vec<u8>) -> io::Result<bool> {
|
||||
self.dictionary.ord_to_term(ord, output)
|
||||
}
|
||||
|
||||
pub fn term_ords(&self) -> &Column<u64> {
|
||||
/// Returns the number of rows in the column.
|
||||
pub fn num_rows(&self) -> RowId {
|
||||
self.term_ord_column.num_rows()
|
||||
}
|
||||
|
||||
/// Returns the column of ordinals
|
||||
pub fn ords(&self) -> &Column<u64> {
|
||||
&self.term_ord_column
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for BytesColumn {
|
||||
type Target = ColumnIndex<'static>;
|
||||
#[derive(Clone)]
|
||||
pub struct StrColumn(BytesColumn);
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&**self.term_ords()
|
||||
impl From<BytesColumn> for StrColumn {
|
||||
fn from(bytes_col: BytesColumn) -> Self {
|
||||
StrColumn(bytes_col)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{ColumnarReader, ColumnarWriter};
|
||||
impl StrColumn {
|
||||
/// Fills the buffer
|
||||
pub fn ord_to_str(&self, term_ord: u64, output: &mut String) -> io::Result<bool> {
|
||||
unsafe {
|
||||
let buf = output.as_mut_vec();
|
||||
self.0.dictionary.ord_to_term(term_ord, buf)?;
|
||||
// TODO consider remove checks if it hurts performance.
|
||||
if std::str::from_utf8(buf.as_slice()).is_err() {
|
||||
buf.clear();
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::InvalidData,
|
||||
"Not valid utf-8",
|
||||
));
|
||||
}
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for StrColumn {
|
||||
type Target = BytesColumn;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,11 @@ use std::ops::Deref;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::BinarySerializable;
|
||||
pub use dictionary_encoded::BytesColumn;
|
||||
pub use serialize::{open_column_bytes, open_column_u64, serialize_column_u64};
|
||||
pub use dictionary_encoded::{BytesColumn, StrColumn};
|
||||
pub use serialize::{
|
||||
open_column_bytes, open_column_u128, open_column_u64, serialize_column_mappable_to_u128,
|
||||
serialize_column_mappable_to_u64,
|
||||
};
|
||||
|
||||
use crate::column_index::ColumnIndex;
|
||||
use crate::column_values::ColumnValues;
|
||||
@@ -18,21 +21,46 @@ pub struct Column<T> {
|
||||
pub values: Arc<dyn ColumnValues<T>>,
|
||||
}
|
||||
|
||||
use crate::column_index::Set;
|
||||
|
||||
impl<T: PartialOrd> Column<T> {
|
||||
pub fn first(&self, row_id: RowId) -> Option<T> {
|
||||
pub fn get_cardinality(&self) -> Cardinality {
|
||||
self.idx.get_cardinality()
|
||||
}
|
||||
pub fn num_rows(&self) -> RowId {
|
||||
match &self.idx {
|
||||
ColumnIndex::Full => Some(self.values.get_val(row_id)),
|
||||
ColumnIndex::Optional(opt_idx) => {
|
||||
let value_row_idx = opt_idx.rank_if_exists(row_id)?;
|
||||
Some(self.values.get_val(value_row_idx))
|
||||
}
|
||||
ColumnIndex::Multivalued(_multivalued_index) => {
|
||||
todo!();
|
||||
ColumnIndex::Full => self.values.num_vals() as u32,
|
||||
ColumnIndex::Optional(optional_index) => optional_index.num_rows(),
|
||||
ColumnIndex::Multivalued(col_index) => {
|
||||
// The multivalued index contains all value start row_id,
|
||||
// and one extra value at the end with the overall number of rows.
|
||||
col_index.num_vals() - 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn min_value(&self) -> T {
|
||||
self.values.min_value()
|
||||
}
|
||||
pub fn max_value(&self) -> T {
|
||||
self.values.max_value()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialOrd + Copy + Send + Sync + 'static> Column<T> {
|
||||
pub fn first(&self, row_id: RowId) -> Option<T> {
|
||||
self.values(row_id).next()
|
||||
}
|
||||
|
||||
pub fn values(&self, row_id: RowId) -> impl Iterator<Item = T> + '_ {
|
||||
self.value_row_ids(row_id)
|
||||
.map(|value_row_id: RowId| self.values.get_val(value_row_id))
|
||||
}
|
||||
|
||||
pub fn first_or_default_col(self, default_value: T) -> Arc<dyn ColumnValues<T>> {
|
||||
Arc::new(FirstValueWithDefault {
|
||||
column: self,
|
||||
default_value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Deref for Column<T> {
|
||||
@@ -54,3 +82,31 @@ impl BinarySerializable for Cardinality {
|
||||
Ok(cardinality)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO simplify or optimize
|
||||
struct FirstValueWithDefault<T: Copy> {
|
||||
column: Column<T>,
|
||||
default_value: T,
|
||||
}
|
||||
|
||||
impl<T: PartialOrd + Send + Sync + Copy + 'static> ColumnValues<T> for FirstValueWithDefault<T> {
|
||||
fn get_val(&self, idx: u32) -> T {
|
||||
self.column.first(idx).unwrap_or(self.default_value)
|
||||
}
|
||||
|
||||
fn min_value(&self) -> T {
|
||||
self.column.values.min_value()
|
||||
}
|
||||
|
||||
fn max_value(&self) -> T {
|
||||
self.column.values.max_value()
|
||||
}
|
||||
|
||||
fn num_vals(&self) -> u32 {
|
||||
match &self.column.idx {
|
||||
ColumnIndex::Full => self.column.values.num_vals(),
|
||||
ColumnIndex::Optional(optional_idx) => optional_idx.num_rows(),
|
||||
ColumnIndex::Multivalued(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,24 +2,51 @@ use std::io;
|
||||
use std::io::Write;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{CountingWriter, OwnedBytes};
|
||||
use common::OwnedBytes;
|
||||
use sstable::Dictionary;
|
||||
|
||||
use crate::column::{BytesColumn, Column};
|
||||
use crate::column_index::{serialize_column_index, SerializableColumnIndex};
|
||||
use crate::column_values::serialize::serialize_column_values_u128;
|
||||
use crate::column_values::{
|
||||
serialize_column_values, ColumnValues, MonotonicallyMappableToU64, ALL_CODEC_TYPES,
|
||||
serialize_column_values, ColumnValues, FastFieldCodecType, MonotonicallyMappableToU128,
|
||||
MonotonicallyMappableToU64,
|
||||
};
|
||||
pub fn serialize_column_u64<T: MonotonicallyMappableToU64>(
|
||||
|
||||
pub fn serialize_column_mappable_to_u128<
|
||||
F: Fn() -> I,
|
||||
I: Iterator<Item = T>,
|
||||
T: MonotonicallyMappableToU128,
|
||||
>(
|
||||
column_index: SerializableColumnIndex<'_>,
|
||||
column_values: F,
|
||||
num_vals: u32,
|
||||
output: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
let column_index_num_bytes = serialize_column_index(column_index, output)?;
|
||||
serialize_column_values_u128(
|
||||
|| column_values().map(|val| val.to_u128()),
|
||||
num_vals,
|
||||
output,
|
||||
)?;
|
||||
output.write_all(&column_index_num_bytes.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn serialize_column_mappable_to_u64<T: MonotonicallyMappableToU64>(
|
||||
column_index: SerializableColumnIndex<'_>,
|
||||
column_values: &impl ColumnValues<T>,
|
||||
output: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
let mut counting_writer = CountingWriter::wrap(output);
|
||||
serialize_column_index(column_index, &mut counting_writer)?;
|
||||
let column_index_num_bytes = counting_writer.written_bytes() as u32;
|
||||
let output = counting_writer.finish();
|
||||
serialize_column_values(column_values, &ALL_CODEC_TYPES[..], output)?;
|
||||
let column_index_num_bytes = serialize_column_index(column_index, output)?;
|
||||
serialize_column_values(
|
||||
column_values,
|
||||
&[
|
||||
FastFieldCodecType::Bitpacked,
|
||||
FastFieldCodecType::BlockwiseLinear,
|
||||
],
|
||||
output,
|
||||
)?;
|
||||
output.write_all(&column_index_num_bytes.to_le_bytes())?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -41,14 +68,34 @@ pub fn open_column_u64<T: MonotonicallyMappableToU64>(bytes: OwnedBytes) -> io::
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_column_bytes(data: OwnedBytes) -> io::Result<BytesColumn> {
|
||||
pub fn open_column_u128<T: MonotonicallyMappableToU128>(
|
||||
bytes: OwnedBytes,
|
||||
) -> io::Result<Column<T>> {
|
||||
let (body, column_index_num_bytes_payload) = bytes.rsplit(4);
|
||||
let column_index_num_bytes = u32::from_le_bytes(
|
||||
column_index_num_bytes_payload
|
||||
.as_slice()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
let (column_index_data, column_values_data) = body.split(column_index_num_bytes as usize);
|
||||
let column_index = crate::column_index::open_column_index(column_index_data)?;
|
||||
let column_values = crate::column_values::open_u128_mapped(column_values_data)?;
|
||||
Ok(Column {
|
||||
idx: column_index,
|
||||
values: column_values,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn open_column_bytes<T: From<BytesColumn>>(data: OwnedBytes) -> io::Result<T> {
|
||||
let (body, dictionary_len_bytes) = data.rsplit(4);
|
||||
let dictionary_len = u32::from_le_bytes(dictionary_len_bytes.as_slice().try_into().unwrap());
|
||||
let (dictionary_bytes, column_bytes) = body.split(dictionary_len as usize);
|
||||
let dictionary = Arc::new(Dictionary::from_bytes(dictionary_bytes)?);
|
||||
let term_ord_column = crate::column::open_column_u64::<u64>(column_bytes)?;
|
||||
Ok(BytesColumn {
|
||||
let bytes_column = BytesColumn {
|
||||
dictionary,
|
||||
term_ord_column,
|
||||
})
|
||||
};
|
||||
Ok(bytes_column.into())
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ mod multivalued_index;
|
||||
mod optional_index;
|
||||
mod serialize;
|
||||
|
||||
use std::ops::Range;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub use optional_index::{OptionalIndex, SerializableOptionalIndex, Set};
|
||||
@@ -14,8 +15,12 @@ use crate::{Cardinality, RowId};
|
||||
pub enum ColumnIndex<'a> {
|
||||
Full,
|
||||
Optional(OptionalIndex),
|
||||
// TODO remove the Arc<dyn> apart from serialization this is not
|
||||
// dynamic at all.
|
||||
// TODO Remove the static by fixing the codec if possible.
|
||||
/// The column values enclosed contains for all row_id,
|
||||
/// the value start_index.
|
||||
///
|
||||
/// In addition, at index num_rows, an extra value is added
|
||||
/// containing the overal number of values.
|
||||
Multivalued(Arc<dyn ColumnValues<RowId> + 'a>),
|
||||
}
|
||||
|
||||
@@ -28,13 +33,22 @@ impl<'a> ColumnIndex<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn num_rows(&self) -> RowId {
|
||||
pub fn value_row_ids(&self, row_id: RowId) -> Range<RowId> {
|
||||
match self {
|
||||
ColumnIndex::Full => {
|
||||
todo!()
|
||||
ColumnIndex::Full => row_id..row_id + 1,
|
||||
ColumnIndex::Optional(optional_index) => {
|
||||
if let Some(val) = optional_index.rank_if_exists(row_id) {
|
||||
val..val + 1
|
||||
} else {
|
||||
0..0
|
||||
}
|
||||
}
|
||||
ColumnIndex::Multivalued(multivalued_index) => {
|
||||
let multivalued_index_ref = &**multivalued_index;
|
||||
let start: u32 = multivalued_index_ref.get_val(row_id);
|
||||
let end: u32 = multivalued_index_ref.get_val(row_id + 1);
|
||||
start..end
|
||||
}
|
||||
ColumnIndex::Optional(optional_index) => optional_index.num_rows(),
|
||||
ColumnIndex::Multivalued(multivalued_index) => multivalued_index.num_vals() - 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,11 +11,11 @@ use crate::RowId;
|
||||
pub struct MultivaluedIndex(Arc<dyn ColumnValues<RowId>>);
|
||||
|
||||
pub fn serialize_multivalued_index(
|
||||
multivalued_index: MultivaluedIndex,
|
||||
multivalued_index: &dyn ColumnValues<RowId>,
|
||||
output: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
crate::column_values::serialize_column_values(
|
||||
&*multivalued_index.0,
|
||||
&*multivalued_index,
|
||||
&[FastFieldCodecType::Bitpacked, FastFieldCodecType::Linear],
|
||||
output,
|
||||
)?;
|
||||
@@ -23,5 +23,7 @@ pub fn serialize_multivalued_index(
|
||||
}
|
||||
|
||||
pub fn open_multivalued_index(bytes: OwnedBytes) -> io::Result<Arc<dyn ColumnValues<RowId>>> {
|
||||
todo!();
|
||||
let start_index_column: Arc<dyn ColumnValues<RowId>> =
|
||||
crate::column_values::open_u64_mapped(bytes)?;
|
||||
Ok(start_index_column)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mod set_block;
|
||||
mod dense;
|
||||
mod sparse;
|
||||
|
||||
pub use set_block::{DenseBlock, DenseBlockCodec, DENSE_BLOCK_NUM_BYTES};
|
||||
pub use dense::{DenseBlock, DenseBlockCodec, DENSE_BLOCK_NUM_BYTES};
|
||||
pub use sparse::{SparseBlock, SparseBlockCodec};
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use crate::column_index::optional_index::set_block::set_block::DENSE_BLOCK_NUM_BYTES;
|
||||
use crate::column_index::optional_index::set_block::{DenseBlockCodec, SparseBlockCodec};
|
||||
use crate::column_index::optional_index::set_block::{
|
||||
DenseBlockCodec, SparseBlockCodec, DENSE_BLOCK_NUM_BYTES,
|
||||
};
|
||||
use crate::column_index::optional_index::{Set, SetCodec};
|
||||
|
||||
fn test_set_helper<C: SetCodec<Item = u16>>(vals: &[u16]) -> usize {
|
||||
@@ -51,6 +52,7 @@ fn test_sparse_block_set_u16_max() {
|
||||
use proptest::prelude::*;
|
||||
|
||||
proptest! {
|
||||
#![proptest_config(ProptestConfig::with_cases(1))]
|
||||
#[test]
|
||||
fn test_prop_test_dense(els in proptest::collection::btree_set(0..=u16::MAX, 0..=u16::MAX as usize)) {
|
||||
let vals: Vec<u16> = els.into_iter().collect();
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
use common::OwnedBytes;
|
||||
use common::{CountingWriter, OwnedBytes};
|
||||
|
||||
use crate::column_index::multivalued_index::{serialize_multivalued_index, MultivaluedIndex};
|
||||
use crate::column_index::multivalued_index::serialize_multivalued_index;
|
||||
use crate::column_index::optional_index::serialize_optional_index;
|
||||
use crate::column_index::{ColumnIndex, SerializableOptionalIndex};
|
||||
use crate::Cardinality;
|
||||
use crate::column_values::ColumnValues;
|
||||
use crate::{Cardinality, RowId};
|
||||
|
||||
pub enum SerializableColumnIndex<'a> {
|
||||
Full,
|
||||
Optional(Box<dyn SerializableOptionalIndex<'a> + 'a>),
|
||||
// TODO remove the Arc<dyn> apart from serialization this is not
|
||||
// dynamic at all.
|
||||
Multivalued(MultivaluedIndex),
|
||||
Multivalued(Box<dyn ColumnValues<RowId> + 'a>),
|
||||
}
|
||||
|
||||
impl<'a> SerializableColumnIndex<'a> {
|
||||
@@ -29,19 +30,21 @@ impl<'a> SerializableColumnIndex<'a> {
|
||||
pub fn serialize_column_index(
|
||||
column_index: SerializableColumnIndex,
|
||||
output: &mut impl Write,
|
||||
) -> io::Result<()> {
|
||||
) -> io::Result<u32> {
|
||||
let mut output = CountingWriter::wrap(output);
|
||||
let cardinality = column_index.get_cardinality().to_code();
|
||||
output.write_all(&[cardinality])?;
|
||||
match column_index {
|
||||
SerializableColumnIndex::Full => {}
|
||||
SerializableColumnIndex::Optional(optional_index) => {
|
||||
serialize_optional_index(&*optional_index, output)?
|
||||
serialize_optional_index(&*optional_index, &mut output)?
|
||||
}
|
||||
SerializableColumnIndex::Multivalued(multivalued_index) => {
|
||||
serialize_multivalued_index(multivalued_index, output)?
|
||||
serialize_multivalued_index(&*multivalued_index, &mut output)?
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
let column_index_num_bytes = output.written_bytes() as u32;
|
||||
Ok(column_index_num_bytes)
|
||||
}
|
||||
|
||||
pub fn open_column_index(mut bytes: OwnedBytes) -> io::Result<ColumnIndex<'static>> {
|
||||
|
||||
@@ -78,6 +78,32 @@ pub trait ColumnValues<T: PartialOrd = u64>: Send + Sync {
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy + PartialOrd> ColumnValues<T> for std::sync::Arc<dyn ColumnValues<T>> {
|
||||
fn get_val(&self, idx: u32) -> T {
|
||||
self.as_ref().get_val(idx)
|
||||
}
|
||||
|
||||
fn min_value(&self) -> T {
|
||||
self.as_ref().min_value()
|
||||
}
|
||||
|
||||
fn max_value(&self) -> T {
|
||||
self.as_ref().max_value()
|
||||
}
|
||||
|
||||
fn num_vals(&self) -> u32 {
|
||||
self.as_ref().num_vals()
|
||||
}
|
||||
|
||||
fn iter<'b>(&'b self) -> Box<dyn Iterator<Item = T> + 'b> {
|
||||
self.as_ref().iter()
|
||||
}
|
||||
|
||||
fn get_range(&self, start: u64, output: &mut [T]) {
|
||||
self.as_ref().get_range(start, output)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, C: ColumnValues<T> + ?Sized, T: Copy + PartialOrd> ColumnValues<T> for &'a C {
|
||||
fn get_val(&self, idx: u32) -> T {
|
||||
(*self).get_val(idx)
|
||||
|
||||
@@ -28,7 +28,7 @@ mod compact_space;
|
||||
mod line;
|
||||
mod linear;
|
||||
pub(crate) mod monotonic_mapping;
|
||||
// mod monotonic_mapping_u128;
|
||||
pub(crate) mod monotonic_mapping_u128;
|
||||
|
||||
mod column;
|
||||
mod column_with_cardinality;
|
||||
@@ -37,8 +37,10 @@ pub mod serialize;
|
||||
|
||||
pub use self::column::{monotonic_map_column, ColumnValues, IterColumn, VecColumn};
|
||||
pub use self::monotonic_mapping::{MonotonicallyMappableToU64, StrictlyMonotonicFn};
|
||||
// pub use self::monotonic_mapping_u128::MonotonicallyMappableToU128;
|
||||
pub use self::serialize::{serialize_and_load, serialize_column_values, NormalizedHeader};
|
||||
pub use self::monotonic_mapping_u128::MonotonicallyMappableToU128;
|
||||
#[cfg(test)]
|
||||
pub use self::serialize::tests::serialize_and_load;
|
||||
pub use self::serialize::{serialize_column_values, NormalizedHeader};
|
||||
use crate::column_values::bitpacked::BitpackedCodec;
|
||||
use crate::column_values::blockwise_linear::BlockwiseLinearCodec;
|
||||
use crate::column_values::linear::LinearCodec;
|
||||
@@ -122,19 +124,17 @@ impl U128FastFieldCodecType {
|
||||
}
|
||||
|
||||
/// Returns the correct codec reader wrapped in the `Arc` for the data.
|
||||
// pub fn open_u128<Item: MonotonicallyMappableToU128>(
|
||||
// bytes: OwnedBytes,
|
||||
// ) -> io::Result<Arc<dyn Column<Item>>> {
|
||||
// todo!();
|
||||
// // let (bytes, _format_version) = read_format_version(bytes)?;
|
||||
// // let (mut bytes, _null_index_footer) = read_null_index_footer(bytes)?;
|
||||
// // let header = U128Header::deserialize(&mut bytes)?;
|
||||
// // assert_eq!(header.codec_type, U128FastFieldCodecType::CompactSpace);
|
||||
// // let reader = CompactSpaceDecompressor::open(bytes)?;
|
||||
// // let inverted: StrictlyMonotonicMappingInverter<StrictlyMonotonicMappingToInternal<Item>> =
|
||||
// // StrictlyMonotonicMappingToInternal::<Item>::new().into();
|
||||
// // Ok(Arc::new(monotonic_map_column(reader, inverted)))
|
||||
// }
|
||||
pub fn open_u128_mapped<T: MonotonicallyMappableToU128>(
|
||||
mut bytes: OwnedBytes,
|
||||
) -> io::Result<Arc<dyn ColumnValues<T>>> {
|
||||
let header = U128Header::deserialize(&mut bytes)?;
|
||||
assert_eq!(header.codec_type, U128FastFieldCodecType::CompactSpace);
|
||||
let reader = CompactSpaceDecompressor::open(bytes)?;
|
||||
|
||||
let inverted: StrictlyMonotonicMappingInverter<StrictlyMonotonicMappingToInternal<T>> =
|
||||
StrictlyMonotonicMappingToInternal::<T>::new().into();
|
||||
Ok(Arc::new(monotonic_map_column(reader, inverted)))
|
||||
}
|
||||
|
||||
/// Returns the correct codec reader wrapped in the `Arc` for the data.
|
||||
pub fn open_u64_mapped<T: MonotonicallyMappableToU64>(
|
||||
@@ -198,13 +198,6 @@ pub(crate) trait FastFieldCodec: 'static {
|
||||
fn estimate(column: &dyn ColumnValues) -> Option<f32>;
|
||||
}
|
||||
|
||||
/// The list of all available codecs for u64 convertible data.
|
||||
pub const ALL_CODEC_TYPES: [FastFieldCodecType; 3] = [
|
||||
FastFieldCodecType::Bitpacked,
|
||||
FastFieldCodecType::BlockwiseLinear,
|
||||
FastFieldCodecType::Linear,
|
||||
];
|
||||
|
||||
#[cfg(all(test, feature = "unstable"))]
|
||||
mod bench {
|
||||
use std::sync::Arc;
|
||||
|
||||
@@ -2,6 +2,7 @@ use std::marker::PhantomData;
|
||||
|
||||
use fastdivide::DividerU64;
|
||||
|
||||
use super::MonotonicallyMappableToU128;
|
||||
use crate::RowId;
|
||||
|
||||
/// Monotonic maps a value to u64 value space.
|
||||
@@ -80,21 +81,20 @@ impl<T> StrictlyMonotonicMappingToInternal<T> {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
// impl<External: MonotonicallyMappableToU128, T: MonotonicallyMappableToU128>
|
||||
// StrictlyMonotonicFn<External, u128> for StrictlyMonotonicMappingToInternal<T>
|
||||
// where T: MonotonicallyMappableToU128
|
||||
// {
|
||||
// #[inline(always)]
|
||||
// fn mapping(&self, inp: External) -> u128 {
|
||||
// External::to_u128(inp)
|
||||
// }
|
||||
impl<External: MonotonicallyMappableToU128, T: MonotonicallyMappableToU128>
|
||||
StrictlyMonotonicFn<External, u128> for StrictlyMonotonicMappingToInternal<T>
|
||||
where T: MonotonicallyMappableToU128
|
||||
{
|
||||
#[inline(always)]
|
||||
fn mapping(&self, inp: External) -> u128 {
|
||||
External::to_u128(inp)
|
||||
}
|
||||
|
||||
// #[inline(always)]
|
||||
// fn inverse(&self, out: u128) -> External {
|
||||
// External::from_u128(out)
|
||||
// }
|
||||
// }
|
||||
#[inline(always)]
|
||||
fn inverse(&self, out: u128) -> External {
|
||||
External::from_u128(out)
|
||||
}
|
||||
}
|
||||
|
||||
impl<External: MonotonicallyMappableToU64, T: MonotonicallyMappableToU64>
|
||||
StrictlyMonotonicFn<External, u64> for StrictlyMonotonicMappingToInternal<T>
|
||||
@@ -194,6 +194,20 @@ impl MonotonicallyMappableToU64 for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl MonotonicallyMappableToU64 for crate::DateTime {
|
||||
#[inline(always)]
|
||||
fn to_u64(self) -> u64 {
|
||||
common::i64_to_u64(self.timestamp_micros)
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn from_u64(val: u64) -> Self {
|
||||
crate::DateTime {
|
||||
timestamp_micros: common::u64_to_i64(val),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MonotonicallyMappableToU64 for bool {
|
||||
#[inline(always)]
|
||||
fn to_u64(self) -> u64 {
|
||||
|
||||
@@ -19,9 +19,8 @@
|
||||
|
||||
use std::io;
|
||||
use std::num::NonZeroU64;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::{BinarySerializable, OwnedBytes, VInt};
|
||||
use common::{BinarySerializable, VInt};
|
||||
use log::warn;
|
||||
|
||||
use super::bitpacked::BitpackedCodec;
|
||||
@@ -33,8 +32,9 @@ use super::monotonic_mapping::{
|
||||
};
|
||||
use super::{
|
||||
monotonic_map_column, ColumnValues, FastFieldCodec, FastFieldCodecType,
|
||||
MonotonicallyMappableToU64, U128FastFieldCodecType, VecColumn, ALL_CODEC_TYPES,
|
||||
MonotonicallyMappableToU64, U128FastFieldCodecType,
|
||||
};
|
||||
use crate::column_values::compact_space::CompactSpaceCompressor;
|
||||
|
||||
/// The normalized header gives some parameters after applying the following
|
||||
/// normalization of the vector:
|
||||
@@ -160,54 +160,22 @@ impl BinarySerializable for Header {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return estimated compression for given codec in the value range [0.0..1.0], where 1.0 means no
|
||||
/// compression.
|
||||
pub(crate) fn estimate<T: MonotonicallyMappableToU64>(
|
||||
typed_column: impl ColumnValues<T>,
|
||||
codec_type: FastFieldCodecType,
|
||||
) -> Option<f32> {
|
||||
let column = monotonic_map_column(typed_column, StrictlyMonotonicMappingToInternal::<T>::new());
|
||||
let min_value = column.min_value();
|
||||
let gcd = super::gcd::find_gcd(column.iter().map(|val| val - min_value))
|
||||
.filter(|gcd| gcd.get() > 1u64);
|
||||
let mapping = StrictlyMonotonicMappingToInternalGCDBaseval::new(
|
||||
gcd.map(|gcd| gcd.get()).unwrap_or(1u64),
|
||||
min_value,
|
||||
);
|
||||
let normalized_column = monotonic_map_column(&column, mapping);
|
||||
match codec_type {
|
||||
FastFieldCodecType::Bitpacked => BitpackedCodec::estimate(&normalized_column),
|
||||
FastFieldCodecType::Linear => LinearCodec::estimate(&normalized_column),
|
||||
FastFieldCodecType::BlockwiseLinear => BlockwiseLinearCodec::estimate(&normalized_column),
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
/// Serializes u128 values with the compact space codec.
|
||||
// pub fn serialize_u128_new<F: Fn() -> I, I: Iterator<Item = u128>>(
|
||||
// value_index: ColumnIndex,
|
||||
// iter_gen: F,
|
||||
// num_vals: u32,
|
||||
// output: &mut impl io::Write,
|
||||
// ) -> io::Result<()> {
|
||||
// let header = U128Header {
|
||||
// num_vals,
|
||||
// codec_type: U128FastFieldCodecType::CompactSpace,
|
||||
// };
|
||||
// header.serialize(output)?;
|
||||
// let compressor = CompactSpaceCompressor::train_from(iter_gen(), num_vals);
|
||||
// compressor.compress_into(iter_gen(), output).unwrap();
|
||||
pub fn serialize_column_values_u128<F: Fn() -> I, I: Iterator<Item = u128>>(
|
||||
iter_gen: F,
|
||||
num_vals: u32,
|
||||
output: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
let header = U128Header {
|
||||
num_vals,
|
||||
codec_type: U128FastFieldCodecType::CompactSpace,
|
||||
};
|
||||
header.serialize(output)?;
|
||||
let compressor = CompactSpaceCompressor::train_from(iter_gen(), num_vals);
|
||||
compressor.compress_into(iter_gen(), output)?;
|
||||
|
||||
// let null_index_footer = ColumnFooter {
|
||||
// cardinality: value_index.get_cardinality(),
|
||||
// null_index_codec: NullIndexCodec::Full,
|
||||
// null_index_byte_range: 0..0,
|
||||
// };
|
||||
// append_null_index_footer(output, null_index_footer)?;
|
||||
// append_format_version(output)?;
|
||||
|
||||
// Ok(())
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Serializes the column with the codec with the best estimate on the data.
|
||||
pub fn serialize_column_values<T: MonotonicallyMappableToU64>(
|
||||
@@ -279,20 +247,29 @@ pub(crate) fn serialize_given_codec(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Helper function to serialize a column (autodetect from all codecs) and then open it
|
||||
pub fn serialize_and_load<T: MonotonicallyMappableToU64 + Ord + Default>(
|
||||
column: &[T],
|
||||
) -> Arc<dyn ColumnValues<T>> {
|
||||
let mut buffer = Vec::new();
|
||||
super::serialize_column_values(&VecColumn::from(&column), &ALL_CODEC_TYPES, &mut buffer)
|
||||
.unwrap();
|
||||
super::open_u64_mapped(OwnedBytes::new(buffer)).unwrap()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
pub mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::OwnedBytes;
|
||||
|
||||
use super::*;
|
||||
use crate::column_values::{open_u64_mapped, VecColumn};
|
||||
|
||||
const ALL_CODEC_TYPES: [FastFieldCodecType; 3] = [
|
||||
FastFieldCodecType::Bitpacked,
|
||||
FastFieldCodecType::Linear,
|
||||
FastFieldCodecType::BlockwiseLinear,
|
||||
];
|
||||
|
||||
/// Helper function to serialize a column (autodetect from all codecs) and then open it
|
||||
pub fn serialize_and_load<T: MonotonicallyMappableToU64 + Ord + Default>(
|
||||
column: &[T],
|
||||
) -> Arc<dyn ColumnValues<T>> {
|
||||
let mut buffer = Vec::new();
|
||||
serialize_column_values(&VecColumn::from(&column), &ALL_CODEC_TYPES, &mut buffer).unwrap();
|
||||
open_u64_mapped(OwnedBytes::new(buffer)).unwrap()
|
||||
}
|
||||
#[test]
|
||||
fn test_serialize_deserialize_u128_header() {
|
||||
let original = U128Header {
|
||||
@@ -319,7 +296,7 @@ mod tests {
|
||||
serialize_column_values(&col, &ALL_CODEC_TYPES, &mut buffer).unwrap();
|
||||
// TODO put the header as a footer so that it serves as a padding.
|
||||
// 5 bytes of header, 1 byte of value, 7 bytes of padding.
|
||||
assert_eq!(buffer.len(), 5 + 1 + 7);
|
||||
assert_eq!(buffer.len(), 5 + 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -328,7 +305,7 @@ mod tests {
|
||||
let col = VecColumn::from(&[true][..]);
|
||||
serialize_column_values(&col, &ALL_CODEC_TYPES, &mut buffer).unwrap();
|
||||
// 5 bytes of header, 0 bytes of value, 7 bytes of padding.
|
||||
assert_eq!(buffer.len(), 5 + 7);
|
||||
assert_eq!(buffer.len(), 5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -338,6 +315,6 @@ mod tests {
|
||||
let col = VecColumn::from(&vals[..]);
|
||||
serialize_column_values(&col, &[FastFieldCodecType::Bitpacked], &mut buffer).unwrap();
|
||||
// Values are stored over 3 bits.
|
||||
assert_eq!(buffer.len(), 7 + (3 * 80 / 8) + 7);
|
||||
assert_eq!(buffer.len(), 7 + (3 * 80 / 8));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,115 +1,149 @@
|
||||
use crate::utils::{place_bits, select_bits};
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use crate::value::NumericalType;
|
||||
use crate::InvalidData;
|
||||
|
||||
/// The column type represents the column type and can fit on 6-bits.
|
||||
///
|
||||
/// - bits[0..3]: Column category type.
|
||||
/// - bits[3..6]: Numerical type if necessary.
|
||||
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy)]
|
||||
/// The column type represents the column type.
|
||||
/// Any changes need to be propagated to `COLUMN_TYPES`.
|
||||
#[derive(Hash, Eq, PartialEq, Debug, Clone, Copy, Ord, PartialOrd)]
|
||||
#[repr(u8)]
|
||||
pub enum ColumnType {
|
||||
Bytes,
|
||||
Numerical(NumericalType),
|
||||
Bool,
|
||||
I64 = 0u8,
|
||||
U64 = 1u8,
|
||||
F64 = 2u8,
|
||||
Bytes = 3u8,
|
||||
Str = 4u8,
|
||||
Bool = 5u8,
|
||||
IpAddr = 6u8,
|
||||
DateTime = 7u8,
|
||||
}
|
||||
|
||||
// The order needs to match _exactly_ the order in the enum
|
||||
const COLUMN_TYPES: [ColumnType; 8] = [
|
||||
ColumnType::I64,
|
||||
ColumnType::U64,
|
||||
ColumnType::F64,
|
||||
ColumnType::Bytes,
|
||||
ColumnType::Str,
|
||||
ColumnType::Bool,
|
||||
ColumnType::IpAddr,
|
||||
ColumnType::DateTime,
|
||||
];
|
||||
|
||||
impl ColumnType {
|
||||
/// Encoded over 6 bits.
|
||||
pub(crate) fn to_code(self) -> u8 {
|
||||
let column_type_category;
|
||||
let numerical_type_code: u8;
|
||||
match self {
|
||||
ColumnType::Bytes => {
|
||||
column_type_category = ColumnTypeCategory::Str;
|
||||
numerical_type_code = 0u8;
|
||||
}
|
||||
ColumnType::Numerical(numerical_type) => {
|
||||
column_type_category = ColumnTypeCategory::Numerical;
|
||||
numerical_type_code = numerical_type.to_code();
|
||||
}
|
||||
ColumnType::Bool => {
|
||||
column_type_category = ColumnTypeCategory::Bool;
|
||||
numerical_type_code = 0u8;
|
||||
}
|
||||
}
|
||||
place_bits::<0, 3>(column_type_category.to_code()) | place_bits::<3, 6>(numerical_type_code)
|
||||
}
|
||||
|
||||
pub(crate) fn try_from_code(code: u8) -> Result<ColumnType, InvalidData> {
|
||||
if select_bits::<6, 8>(code) != 0u8 {
|
||||
return Err(InvalidData);
|
||||
}
|
||||
let column_type_category_code = select_bits::<0, 3>(code);
|
||||
let numerical_type_code = select_bits::<3, 6>(code);
|
||||
let column_type_category = ColumnTypeCategory::try_from_code(column_type_category_code)?;
|
||||
match column_type_category {
|
||||
ColumnTypeCategory::Bool => {
|
||||
if numerical_type_code != 0u8 {
|
||||
return Err(InvalidData);
|
||||
}
|
||||
Ok(ColumnType::Bool)
|
||||
}
|
||||
ColumnTypeCategory::Str => {
|
||||
if numerical_type_code != 0u8 {
|
||||
return Err(InvalidData);
|
||||
}
|
||||
Ok(ColumnType::Bytes)
|
||||
}
|
||||
ColumnTypeCategory::Numerical => {
|
||||
let numerical_type = NumericalType::try_from_code(numerical_type_code)?;
|
||||
Ok(ColumnType::Numerical(numerical_type))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Column types are grouped into different categories that
|
||||
/// corresponds to the different types of `JsonValue` types.
|
||||
///
|
||||
/// The columnar writer will apply coercion rules to make sure that
|
||||
/// at most one column exist per `ColumnTypeCategory`.
|
||||
///
|
||||
/// See also [README.md].
|
||||
#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq, Debug)]
|
||||
#[repr(u8)]
|
||||
pub(crate) enum ColumnTypeCategory {
|
||||
Bool = 0u8,
|
||||
Str = 1u8,
|
||||
Numerical = 2u8,
|
||||
}
|
||||
|
||||
impl ColumnTypeCategory {
|
||||
pub fn to_code(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
pub fn try_from_code(code: u8) -> Result<Self, InvalidData> {
|
||||
match code {
|
||||
0u8 => Ok(Self::Bool),
|
||||
1u8 => Ok(Self::Str),
|
||||
2u8 => Ok(Self::Numerical),
|
||||
_ => Err(InvalidData),
|
||||
pub(crate) fn try_from_code(code: u8) -> Result<ColumnType, InvalidData> {
|
||||
COLUMN_TYPES.get(code as usize).copied().ok_or(InvalidData)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NumericalType> for ColumnType {
|
||||
fn from(numerical_type: NumericalType) -> Self {
|
||||
match numerical_type {
|
||||
NumericalType::I64 => ColumnType::I64,
|
||||
NumericalType::U64 => ColumnType::U64,
|
||||
NumericalType::F64 => ColumnType::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ColumnType {
|
||||
pub fn numerical_type(&self) -> Option<NumericalType> {
|
||||
match self {
|
||||
ColumnType::I64 => Some(NumericalType::I64),
|
||||
ColumnType::U64 => Some(NumericalType::U64),
|
||||
ColumnType::F64 => Some(NumericalType::F64),
|
||||
ColumnType::Bytes
|
||||
| ColumnType::Str
|
||||
| ColumnType::Bool
|
||||
| ColumnType::IpAddr
|
||||
| ColumnType::DateTime => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO remove if possible
|
||||
pub trait HasAssociatedColumnType: 'static + Send + Sync + Copy + PartialOrd {
|
||||
fn column_type() -> ColumnType;
|
||||
fn default_value() -> Self;
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for u64 {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::U64
|
||||
}
|
||||
|
||||
fn default_value() -> Self {
|
||||
0u64
|
||||
}
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for i64 {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::I64
|
||||
}
|
||||
|
||||
fn default_value() -> Self {
|
||||
0i64
|
||||
}
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for f64 {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::F64
|
||||
}
|
||||
|
||||
fn default_value() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for bool {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::Bool
|
||||
}
|
||||
fn default_value() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for crate::DateTime {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::DateTime
|
||||
}
|
||||
fn default_value() -> Self {
|
||||
Default::default()
|
||||
}
|
||||
}
|
||||
|
||||
impl HasAssociatedColumnType for Ipv6Addr {
|
||||
fn column_type() -> ColumnType {
|
||||
ColumnType::IpAddr
|
||||
}
|
||||
|
||||
fn default_value() -> Self {
|
||||
Ipv6Addr::from([0u8; 16])
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::collections::HashSet;
|
||||
|
||||
use super::*;
|
||||
use crate::Cardinality;
|
||||
|
||||
#[test]
|
||||
fn test_column_type_to_code() {
|
||||
let mut column_type_set: HashSet<ColumnType> = HashSet::new();
|
||||
for code in u8::MIN..=u8::MAX {
|
||||
if let Ok(column_type) = ColumnType::try_from_code(code) {
|
||||
assert_eq!(column_type.to_code(), code);
|
||||
assert!(column_type_set.insert(column_type));
|
||||
for (code, expected_column_type) in super::COLUMN_TYPES.iter().copied().enumerate() {
|
||||
if let Ok(column_type) = ColumnType::try_from_code(code as u8) {
|
||||
assert_eq!(column_type, expected_column_type);
|
||||
}
|
||||
}
|
||||
assert_eq!(column_type_set.len(), 2 + 3);
|
||||
for code in COLUMN_TYPES.len() as u8..=u8::MAX {
|
||||
assert!(ColumnType::try_from_code(code as u8).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
248
columnar/src/columnar/merge.rs
Normal file
248
columnar/src/columnar/merge.rs
Normal file
@@ -0,0 +1,248 @@
|
||||
use std::collections::HashMap;
|
||||
use std::io;
|
||||
|
||||
use super::writer::ColumnarSerializer;
|
||||
use crate::columnar::ColumnarReader;
|
||||
use crate::dynamic_column::DynamicColumn;
|
||||
use crate::{Cardinality, ColumnType};
|
||||
|
||||
pub enum MergeDocOrder {
|
||||
/// Columnar tables are simply stacked one above the other.
|
||||
/// If the i-th columnar_readers has n_rows_i rows, then
|
||||
/// in the resulting columnar,
|
||||
/// rows [r0..n_row_0) contains the row of columnar_readers[0], in ordder
|
||||
/// rows [n_row_0..n_row_0 + n_row_1 contains the row of columnar_readers[1], in order.
|
||||
/// ..
|
||||
Stack,
|
||||
/// Some more complex mapping, that can interleaves rows from the different readers and
|
||||
/// possibly drop rows.
|
||||
Complex(()),
|
||||
}
|
||||
|
||||
pub fn merge_columnar(
|
||||
columnar_readers: &[ColumnarReader],
|
||||
mapping: MergeDocOrder,
|
||||
output: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
let mut serializer = ColumnarSerializer::new(output);
|
||||
|
||||
// TODO handle dictionary merge for Str/Bytes column
|
||||
let field_name_to_group = group_columns_for_merge(columnar_readers)?;
|
||||
for (column_name, category_to_columns) in field_name_to_group {
|
||||
for (_category, columns_to_merge) in category_to_columns {
|
||||
let column_type = columns_to_merge[0].column_type();
|
||||
let mut column_serialzier =
|
||||
serializer.serialize_column(column_name.as_bytes(), column_type);
|
||||
merge_columns(
|
||||
column_type,
|
||||
&columns_to_merge,
|
||||
&mapping,
|
||||
&mut column_serialzier,
|
||||
)?;
|
||||
}
|
||||
}
|
||||
serializer.finalize()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Column types are grouped into different categories.
|
||||
/// After merge, all columns belonging to the same category are coerced to
|
||||
/// the same column type.
|
||||
///
|
||||
/// In practise, today, only Numerical colummns are coerced into one type today.
|
||||
///
|
||||
/// See also [README.md].
|
||||
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
|
||||
#[repr(u8)]
|
||||
pub enum ColumnTypeCategory {
|
||||
Bool,
|
||||
Str,
|
||||
Numerical,
|
||||
DateTime,
|
||||
Bytes,
|
||||
IpAddr,
|
||||
}
|
||||
|
||||
impl From<ColumnType> for ColumnTypeCategory {
|
||||
fn from(column_type: ColumnType) -> Self {
|
||||
match column_type {
|
||||
ColumnType::I64 => ColumnTypeCategory::Numerical,
|
||||
ColumnType::U64 => ColumnTypeCategory::Numerical,
|
||||
ColumnType::F64 => ColumnTypeCategory::Numerical,
|
||||
ColumnType::Bytes => ColumnTypeCategory::Bytes,
|
||||
ColumnType::Str => ColumnTypeCategory::Str,
|
||||
ColumnType::Bool => ColumnTypeCategory::Bool,
|
||||
ColumnType::IpAddr => ColumnTypeCategory::IpAddr,
|
||||
ColumnType::DateTime => ColumnTypeCategory::DateTime,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_cardinality(columns: &[DynamicColumn]) -> Cardinality {
|
||||
if columns
|
||||
.iter()
|
||||
.any(|column| column.get_cardinality().is_multivalue())
|
||||
{
|
||||
return Cardinality::Multivalued;
|
||||
}
|
||||
if columns
|
||||
.iter()
|
||||
.any(|column| column.get_cardinality().is_optional())
|
||||
{
|
||||
return Cardinality::Optional;
|
||||
}
|
||||
Cardinality::Full
|
||||
}
|
||||
|
||||
pub fn compute_num_docs(columns: &[DynamicColumn], mapping: &MergeDocOrder) -> usize {
|
||||
// TODO handle deletes
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
pub fn merge_columns(
|
||||
column_type: ColumnType,
|
||||
columns: &[DynamicColumn],
|
||||
mapping: &MergeDocOrder,
|
||||
column_serializer: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
let cardinality = detect_cardinality(columns);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn group_columns_for_merge(
|
||||
columnar_readers: &[ColumnarReader],
|
||||
) -> io::Result<HashMap<String, HashMap<ColumnTypeCategory, Vec<DynamicColumn>>>> {
|
||||
// Each column name may have multiple types of column associated.
|
||||
// For merging we are interested in the same column type category since they can be merged.
|
||||
let mut field_name_to_group: HashMap<String, HashMap<ColumnTypeCategory, Vec<DynamicColumn>>> =
|
||||
HashMap::new();
|
||||
|
||||
for columnar_reader in columnar_readers {
|
||||
let column_name_and_handle = columnar_reader.list_columns()?;
|
||||
for (column_name, handle) in column_name_and_handle {
|
||||
let column_type_to_handles = field_name_to_group
|
||||
.entry(column_name.to_string())
|
||||
.or_default();
|
||||
|
||||
let columns = column_type_to_handles
|
||||
.entry(handle.column_type().into())
|
||||
.or_default();
|
||||
columns.push(handle.open()?);
|
||||
}
|
||||
}
|
||||
|
||||
normalize_columns(&mut field_name_to_group);
|
||||
|
||||
Ok(field_name_to_group)
|
||||
}
|
||||
|
||||
/// Coerce numerical type columns to the same type
|
||||
/// TODO rename to `coerce_columns`
|
||||
fn normalize_columns(map: &mut HashMap<String, HashMap<ColumnTypeCategory, Vec<DynamicColumn>>>) {
|
||||
for (_field_name, type_category_to_columns) in map.iter_mut() {
|
||||
for (type_category, columns) in type_category_to_columns {
|
||||
if type_category == &ColumnTypeCategory::Numerical {
|
||||
let casted_columns = cast_to_common_numerical_column(&columns);
|
||||
*columns = casted_columns;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Receives a list of columns of numerical types (u64, i64, f64)
|
||||
///
|
||||
/// Returns a list of `DynamicColumn` which are all of the same numerical type
|
||||
fn cast_to_common_numerical_column(columns: &[DynamicColumn]) -> Vec<DynamicColumn> {
|
||||
assert!(columns
|
||||
.iter()
|
||||
.all(|column| column.column_type().numerical_type().is_some()));
|
||||
let coerce_to_i64: Vec<_> = columns
|
||||
.iter()
|
||||
.filter_map(|column| column.clone().coerce_to_i64())
|
||||
.collect();
|
||||
|
||||
if coerce_to_i64.len() == columns.len() {
|
||||
return coerce_to_i64;
|
||||
}
|
||||
|
||||
let coerce_to_u64: Vec<_> = columns
|
||||
.iter()
|
||||
.filter_map(|column| column.clone().coerce_to_u64())
|
||||
.collect();
|
||||
|
||||
if coerce_to_u64.len() == columns.len() {
|
||||
return coerce_to_u64;
|
||||
}
|
||||
|
||||
columns
|
||||
.iter()
|
||||
.map(|column| {
|
||||
column
|
||||
.clone()
|
||||
.coerce_to_f64()
|
||||
.expect("couldn't cast column to f64")
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::ColumnarWriter;
|
||||
|
||||
#[test]
|
||||
fn test_column_coercion() {
|
||||
// i64 type
|
||||
let columnar1 = {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_numerical(1u32, "numbers", 1i64);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(2, &mut buffer).unwrap();
|
||||
ColumnarReader::open(buffer).unwrap()
|
||||
};
|
||||
// u64 type
|
||||
let columnar2 = {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_numerical(1u32, "numbers", u64::MAX - 100);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(2, &mut buffer).unwrap();
|
||||
ColumnarReader::open(buffer).unwrap()
|
||||
};
|
||||
|
||||
// f64 type
|
||||
let columnar3 = {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_numerical(1u32, "numbers", 30.5);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(2, &mut buffer).unwrap();
|
||||
ColumnarReader::open(buffer).unwrap()
|
||||
};
|
||||
|
||||
let column_map =
|
||||
group_columns_for_merge(&[columnar1.clone(), columnar2.clone(), columnar3.clone()])
|
||||
.unwrap();
|
||||
assert_eq!(column_map.len(), 1);
|
||||
let cat_to_columns = column_map.get("numbers").unwrap();
|
||||
assert_eq!(cat_to_columns.len(), 1);
|
||||
|
||||
let numerical = cat_to_columns.get(&ColumnTypeCategory::Numerical).unwrap();
|
||||
assert!(numerical.iter().all(|column| column.is_f64()));
|
||||
|
||||
let column_map = group_columns_for_merge(&[columnar1.clone(), columnar1.clone()]).unwrap();
|
||||
assert_eq!(column_map.len(), 1);
|
||||
let cat_to_columns = column_map.get("numbers").unwrap();
|
||||
assert_eq!(cat_to_columns.len(), 1);
|
||||
let numerical = cat_to_columns.get(&ColumnTypeCategory::Numerical).unwrap();
|
||||
assert!(numerical.iter().all(|column| column.is_i64()));
|
||||
|
||||
let column_map = group_columns_for_merge(&[columnar2.clone(), columnar2.clone()]).unwrap();
|
||||
assert_eq!(column_map.len(), 1);
|
||||
let cat_to_columns = column_map.get("numbers").unwrap();
|
||||
assert_eq!(cat_to_columns.len(), 1);
|
||||
let numerical = cat_to_columns.get(&ColumnTypeCategory::Numerical).unwrap();
|
||||
assert!(numerical.iter().all(|column| column.is_u64()));
|
||||
}
|
||||
}
|
||||
1
columnar/src/columnar/merge_index.rs
Normal file
1
columnar/src/columnar/merge_index.rs
Normal file
@@ -0,0 +1 @@
|
||||
|
||||
@@ -1,28 +1,11 @@
|
||||
// Copyright (C) 2022 Quickwit, Inc.
|
||||
//
|
||||
// Quickwit is offered under the AGPL v3.0 and as commercial software.
|
||||
// For commercial licensing, contact us at hello@quickwit.io.
|
||||
//
|
||||
// AGPL:
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as
|
||||
// published by the Free Software Foundation, either version 3 of the
|
||||
// License, or (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
mod column_type;
|
||||
mod format_version;
|
||||
mod merge;
|
||||
mod merge_index;
|
||||
mod reader;
|
||||
mod writer;
|
||||
|
||||
pub use column_type::ColumnType;
|
||||
pub use column_type::{ColumnType, HasAssociatedColumnType};
|
||||
pub use merge::{merge_columnar, MergeDocOrder};
|
||||
pub use reader::ColumnarReader;
|
||||
pub use writer::ColumnarWriter;
|
||||
|
||||
@@ -13,6 +13,7 @@ fn io_invalid_data(msg: String) -> io::Error {
|
||||
|
||||
/// The ColumnarReader makes it possible to access a set of columns
|
||||
/// associated to field names.
|
||||
#[derive(Clone)]
|
||||
pub struct ColumnarReader {
|
||||
column_dictionary: Dictionary<RangeSSTable>,
|
||||
column_data: FileSlice,
|
||||
@@ -44,7 +45,7 @@ impl ColumnarReader {
|
||||
})
|
||||
}
|
||||
|
||||
// TODO fix ugly API
|
||||
// TODO Add unit tests
|
||||
pub fn list_columns(&self) -> io::Result<Vec<(String, DynamicColumnHandle)>> {
|
||||
let mut stream = self.column_dictionary.stream()?;
|
||||
let mut results = Vec::new();
|
||||
@@ -55,7 +56,8 @@ impl ColumnarReader {
|
||||
.map_err(|_| io_invalid_data(format!("Unknown column code `{column_code}`")))?;
|
||||
let range = stream.value().clone();
|
||||
let column_name =
|
||||
String::from_utf8_lossy(&key_bytes[..key_bytes.len() - 1]).to_string();
|
||||
// The last two bytes are respectively the 0u8 separator and the column_type.
|
||||
String::from_utf8_lossy(&key_bytes[..key_bytes.len() - 2]).to_string();
|
||||
let file_slice = self
|
||||
.column_data
|
||||
.slice(range.start as usize..range.end as usize);
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use crate::dictionary::UnorderedId;
|
||||
use crate::utils::{place_bits, pop_first_byte, select_bits};
|
||||
use crate::value::NumericalValue;
|
||||
@@ -25,12 +27,12 @@ struct ColumnOperationMetadata {
|
||||
|
||||
impl ColumnOperationMetadata {
|
||||
fn to_code(self) -> u8 {
|
||||
place_bits::<0, 4>(self.len) | place_bits::<4, 8>(self.op_type.to_code())
|
||||
place_bits::<0, 6>(self.len) | place_bits::<6, 8>(self.op_type.to_code())
|
||||
}
|
||||
|
||||
fn try_from_code(code: u8) -> Result<Self, InvalidData> {
|
||||
let len = select_bits::<0, 4>(code);
|
||||
let typ_code = select_bits::<4, 8>(code);
|
||||
let len = select_bits::<0, 6>(code);
|
||||
let typ_code = select_bits::<6, 8>(code);
|
||||
let column_type = ColumnOperationType::try_from_code(typ_code)?;
|
||||
Ok(ColumnOperationMetadata {
|
||||
op_type: column_type,
|
||||
@@ -142,9 +144,21 @@ impl SymbolValue for bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl SymbolValue for Ipv6Addr {
|
||||
fn serialize(self, buffer: &mut [u8]) -> u8 {
|
||||
buffer[0..16].copy_from_slice(&self.octets());
|
||||
16
|
||||
}
|
||||
|
||||
fn deserialize(bytes: &[u8]) -> Self {
|
||||
let octets: [u8; 16] = bytes[0..16].try_into().unwrap();
|
||||
Ipv6Addr::from(octets)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct MiniBuffer {
|
||||
pub bytes: [u8; 10],
|
||||
pub bytes: [u8; 17],
|
||||
pub len: u8,
|
||||
}
|
||||
|
||||
|
||||
@@ -102,18 +102,29 @@ pub(crate) struct NumericalColumnWriter {
|
||||
column_writer: ColumnWriter,
|
||||
}
|
||||
|
||||
impl NumericalColumnWriter {
|
||||
pub fn force_numerical_type(&mut self, numerical_type: NumericalType) {
|
||||
assert!(self
|
||||
.compatible_numerical_types
|
||||
.is_type_accepted(numerical_type));
|
||||
self.compatible_numerical_types = CompatibleNumericalTypes::StaticType(numerical_type);
|
||||
}
|
||||
}
|
||||
|
||||
/// State used to store what types are still acceptable
|
||||
/// after having seen a set of numerical values.
|
||||
#[derive(Clone, Copy)]
|
||||
struct CompatibleNumericalTypes {
|
||||
all_values_within_i64_range: bool,
|
||||
all_values_within_u64_range: bool,
|
||||
// f64 is always acceptable.
|
||||
enum CompatibleNumericalTypes {
|
||||
Dynamic {
|
||||
all_values_within_i64_range: bool,
|
||||
all_values_within_u64_range: bool,
|
||||
},
|
||||
StaticType(NumericalType),
|
||||
}
|
||||
|
||||
impl Default for CompatibleNumericalTypes {
|
||||
fn default() -> CompatibleNumericalTypes {
|
||||
CompatibleNumericalTypes {
|
||||
CompatibleNumericalTypes::Dynamic {
|
||||
all_values_within_i64_range: true,
|
||||
all_values_within_u64_range: true,
|
||||
}
|
||||
@@ -121,39 +132,64 @@ impl Default for CompatibleNumericalTypes {
|
||||
}
|
||||
|
||||
impl CompatibleNumericalTypes {
|
||||
fn is_type_accepted(&self, numerical_type: NumericalType) -> bool {
|
||||
match self {
|
||||
CompatibleNumericalTypes::Dynamic {
|
||||
all_values_within_i64_range,
|
||||
all_values_within_u64_range,
|
||||
} => match numerical_type {
|
||||
NumericalType::I64 => *all_values_within_i64_range,
|
||||
NumericalType::U64 => *all_values_within_u64_range,
|
||||
NumericalType::F64 => true,
|
||||
},
|
||||
CompatibleNumericalTypes::StaticType(static_numerical_type) => {
|
||||
*static_numerical_type == numerical_type
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn accept_value(&mut self, numerical_value: NumericalValue) {
|
||||
match numerical_value {
|
||||
NumericalValue::I64(val_i64) => {
|
||||
let value_within_u64_range = val_i64 >= 0i64;
|
||||
self.all_values_within_u64_range &= value_within_u64_range;
|
||||
}
|
||||
NumericalValue::U64(val_u64) => {
|
||||
let value_within_i64_range = val_u64 < i64::MAX as u64;
|
||||
self.all_values_within_i64_range &= value_within_i64_range;
|
||||
}
|
||||
NumericalValue::F64(_) => {
|
||||
self.all_values_within_i64_range = false;
|
||||
self.all_values_within_u64_range = false;
|
||||
match self {
|
||||
CompatibleNumericalTypes::Dynamic {
|
||||
all_values_within_i64_range,
|
||||
all_values_within_u64_range,
|
||||
} => match numerical_value {
|
||||
NumericalValue::I64(val_i64) => {
|
||||
let value_within_u64_range = val_i64 >= 0i64;
|
||||
*all_values_within_u64_range &= value_within_u64_range;
|
||||
}
|
||||
NumericalValue::U64(val_u64) => {
|
||||
let value_within_i64_range = val_u64 < i64::MAX as u64;
|
||||
*all_values_within_i64_range &= value_within_i64_range;
|
||||
}
|
||||
NumericalValue::F64(_) => {
|
||||
*all_values_within_i64_range = false;
|
||||
*all_values_within_u64_range = false;
|
||||
}
|
||||
},
|
||||
CompatibleNumericalTypes::StaticType(typ) => {
|
||||
assert_eq!(numerical_value.numerical_type(), *typ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_numerical_type(self) -> NumericalType {
|
||||
if self.all_values_within_i64_range {
|
||||
NumericalType::I64
|
||||
} else if self.all_values_within_u64_range {
|
||||
NumericalType::U64
|
||||
} else {
|
||||
NumericalType::F64
|
||||
for numerical_type in [NumericalType::I64, NumericalType::U64] {
|
||||
if self.is_type_accepted(numerical_type) {
|
||||
return numerical_type;
|
||||
}
|
||||
}
|
||||
NumericalType::F64
|
||||
}
|
||||
}
|
||||
|
||||
impl NumericalColumnWriter {
|
||||
pub fn column_type_and_cardinality(&self, num_docs: RowId) -> (NumericalType, Cardinality) {
|
||||
let numerical_type = self.compatible_numerical_types.to_numerical_type();
|
||||
let cardinality = self.column_writer.get_cardinality(num_docs);
|
||||
(numerical_type, cardinality)
|
||||
pub fn numerical_type(&self) -> NumericalType {
|
||||
self.compatible_numerical_types.to_numerical_type()
|
||||
}
|
||||
|
||||
pub fn cardinality(&self, num_docs: RowId) -> Cardinality {
|
||||
self.column_writer.get_cardinality(num_docs)
|
||||
}
|
||||
|
||||
pub fn record_numerical_value(
|
||||
@@ -175,15 +211,15 @@ impl NumericalColumnWriter {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub(crate) struct StrColumnWriter {
|
||||
#[derive(Copy, Clone)]
|
||||
pub(crate) struct StrOrBytesColumnWriter {
|
||||
pub(crate) dictionary_id: u32,
|
||||
pub(crate) column_writer: ColumnWriter,
|
||||
}
|
||||
|
||||
impl StrColumnWriter {
|
||||
pub(crate) fn with_dictionary_id(dictionary_id: u32) -> StrColumnWriter {
|
||||
StrColumnWriter {
|
||||
impl StrOrBytesColumnWriter {
|
||||
pub(crate) fn with_dictionary_id(dictionary_id: u32) -> StrOrBytesColumnWriter {
|
||||
StrOrBytesColumnWriter {
|
||||
dictionary_id,
|
||||
column_writer: Default::default(),
|
||||
}
|
||||
@@ -262,4 +298,27 @@ mod tests {
|
||||
test_column_writer_coercion_aux(&[1i64.into(), 1u64.into()], NumericalType::I64);
|
||||
test_column_writer_coercion_aux(&[u64::MAX.into(), (-1i64).into()], NumericalType::F64);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_compatible_numerical_types_static_incompatible_type() {
|
||||
let mut compatible_numerical_types =
|
||||
CompatibleNumericalTypes::StaticType(NumericalType::U64);
|
||||
compatible_numerical_types.accept_value(NumericalValue::I64(1i64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compatible_numerical_types_static_different_type_forbidden() {
|
||||
let mut compatible_numerical_types =
|
||||
CompatibleNumericalTypes::StaticType(NumericalType::U64);
|
||||
compatible_numerical_types.accept_value(NumericalValue::U64(u64::MAX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compatible_numerical_types_static() {
|
||||
for typ in [NumericalType::I64, NumericalType::I64, NumericalType::F64] {
|
||||
let compatible_numerical_types = CompatibleNumericalTypes::StaticType(typ);
|
||||
assert_eq!(compatible_numerical_types.to_numerical_type(), typ);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,17 +4,20 @@ mod serializer;
|
||||
mod value_index;
|
||||
|
||||
use std::io;
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use column_operation::ColumnOperation;
|
||||
use common::CountingWriter;
|
||||
use serializer::ColumnarSerializer;
|
||||
pub(crate) use serializer::ColumnarSerializer;
|
||||
use stacker::{Addr, ArenaHashMap, MemoryArena};
|
||||
|
||||
use crate::column_index::SerializableColumnIndex;
|
||||
use crate::column_values::{ColumnValues, MonotonicallyMappableToU64, VecColumn};
|
||||
use crate::columnar::column_type::{ColumnType, ColumnTypeCategory};
|
||||
use crate::column_values::{
|
||||
ColumnValues, MonotonicallyMappableToU128, MonotonicallyMappableToU64, VecColumn,
|
||||
};
|
||||
use crate::columnar::column_type::ColumnType;
|
||||
use crate::columnar::writer::column_writers::{
|
||||
ColumnWriter, NumericalColumnWriter, StrColumnWriter,
|
||||
ColumnWriter, NumericalColumnWriter, StrOrBytesColumnWriter,
|
||||
};
|
||||
use crate::columnar::writer::value_index::{IndexBuilder, PreallocatedIndexBuilders};
|
||||
use crate::dictionary::{DictionaryBuilder, TermIdMapping, UnorderedId};
|
||||
@@ -30,6 +33,7 @@ struct SpareBuffers {
|
||||
u64_values: Vec<u64>,
|
||||
f64_values: Vec<f64>,
|
||||
bool_values: Vec<bool>,
|
||||
ip_addr_values: Vec<Ipv6Addr>,
|
||||
}
|
||||
|
||||
/// Makes it possible to create a new columnar.
|
||||
@@ -47,8 +51,11 @@ struct SpareBuffers {
|
||||
/// ```
|
||||
pub struct ColumnarWriter {
|
||||
numerical_field_hash_map: ArenaHashMap,
|
||||
datetime_field_hash_map: ArenaHashMap,
|
||||
bool_field_hash_map: ArenaHashMap,
|
||||
ip_addr_field_hash_map: ArenaHashMap,
|
||||
bytes_field_hash_map: ArenaHashMap,
|
||||
str_field_hash_map: ArenaHashMap,
|
||||
arena: MemoryArena,
|
||||
// Dictionaries used to store dictionary-encoded values.
|
||||
dictionaries: Vec<DictionaryBuilder>,
|
||||
@@ -60,7 +67,10 @@ impl Default for ColumnarWriter {
|
||||
ColumnarWriter {
|
||||
numerical_field_hash_map: ArenaHashMap::new(10_000),
|
||||
bool_field_hash_map: ArenaHashMap::new(10_000),
|
||||
ip_addr_field_hash_map: ArenaHashMap::new(10_000),
|
||||
bytes_field_hash_map: ArenaHashMap::new(10_000),
|
||||
str_field_hash_map: ArenaHashMap::new(10_000),
|
||||
datetime_field_hash_map: ArenaHashMap::new(10_000),
|
||||
dictionaries: Vec::new(),
|
||||
arena: MemoryArena::default(),
|
||||
buffers: SpareBuffers::default(),
|
||||
@@ -68,20 +78,115 @@ impl Default for ColumnarWriter {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn mutate_or_create_column<V, TMutator>(
|
||||
arena_hash_map: &mut ArenaHashMap,
|
||||
column_name: &str,
|
||||
updater: TMutator,
|
||||
) where
|
||||
V: Copy + 'static,
|
||||
TMutator: FnMut(Option<V>) -> V,
|
||||
{
|
||||
assert!(
|
||||
!column_name.as_bytes().contains(&0u8),
|
||||
"key may not contain the 0 byte"
|
||||
);
|
||||
arena_hash_map.mutate_or_create(column_name.as_bytes(), updater);
|
||||
}
|
||||
|
||||
impl ColumnarWriter {
|
||||
pub fn mem_usage(&self) -> usize {
|
||||
// TODO add dictionary builders.
|
||||
self.arena.mem_usage()
|
||||
+ self.numerical_field_hash_map.mem_usage()
|
||||
+ self.bool_field_hash_map.mem_usage()
|
||||
+ self.bytes_field_hash_map.mem_usage()
|
||||
+ self.str_field_hash_map.mem_usage()
|
||||
+ self.ip_addr_field_hash_map.mem_usage()
|
||||
+ self.datetime_field_hash_map.mem_usage()
|
||||
}
|
||||
|
||||
pub fn record_column_type(&mut self, column_name: &str, column_type: ColumnType) {
|
||||
match column_type {
|
||||
ColumnType::Str | ColumnType::Bytes => {
|
||||
let (hash_map, dictionaries) = (
|
||||
if column_type == ColumnType::Str {
|
||||
&mut self.str_field_hash_map
|
||||
} else {
|
||||
&mut self.bytes_field_hash_map
|
||||
},
|
||||
&mut self.dictionaries,
|
||||
);
|
||||
mutate_or_create_column(
|
||||
hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<StrOrBytesColumnWriter>| {
|
||||
if let Some(column_writer) = column_opt {
|
||||
column_writer
|
||||
} else {
|
||||
let dictionary_id = dictionaries.len() as u32;
|
||||
dictionaries.push(DictionaryBuilder::default());
|
||||
StrOrBytesColumnWriter::with_dictionary_id(dictionary_id)
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
ColumnType::Bool => {
|
||||
mutate_or_create_column(
|
||||
&mut self.bool_field_hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<ColumnWriter>| column_opt.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
ColumnType::DateTime => {
|
||||
mutate_or_create_column(
|
||||
&mut self.datetime_field_hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<ColumnWriter>| column_opt.unwrap_or_default(),
|
||||
);
|
||||
}
|
||||
ColumnType::I64 | ColumnType::F64 | ColumnType::U64 => {
|
||||
let numerical_type = column_type.numerical_type().unwrap();
|
||||
mutate_or_create_column(
|
||||
&mut self.numerical_field_hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<NumericalColumnWriter>| {
|
||||
let mut column: NumericalColumnWriter = column_opt.unwrap_or_default();
|
||||
column.force_numerical_type(numerical_type);
|
||||
column
|
||||
},
|
||||
);
|
||||
}
|
||||
ColumnType::IpAddr => mutate_or_create_column(
|
||||
&mut self.ip_addr_field_hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<ColumnWriter>| column_opt.unwrap_or_default(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn force_numerical_type(&mut self, column_name: &str, numerical_type: NumericalType) {
|
||||
mutate_or_create_column(
|
||||
&mut self.numerical_field_hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<NumericalColumnWriter>| {
|
||||
let mut column: NumericalColumnWriter = column_opt.unwrap_or_default();
|
||||
column.force_numerical_type(numerical_type);
|
||||
column
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn record_numerical<T: Into<NumericalValue> + Copy>(
|
||||
&mut self,
|
||||
doc: RowId,
|
||||
column_name: &str,
|
||||
numerical_value: T,
|
||||
) {
|
||||
assert!(
|
||||
!column_name.as_bytes().contains(&0u8),
|
||||
"key may not contain the 0 byte"
|
||||
);
|
||||
let (hash_map, arena) = (&mut self.numerical_field_hash_map, &mut self.arena);
|
||||
hash_map.mutate_or_create(
|
||||
column_name.as_bytes(),
|
||||
mutate_or_create_column(
|
||||
hash_map,
|
||||
column_name,
|
||||
|column_opt: Option<NumericalColumnWriter>| {
|
||||
let mut column: NumericalColumnWriter = column_opt.unwrap_or_default();
|
||||
column.record_numerical_value(doc, numerical_value.into(), arena);
|
||||
@@ -90,23 +195,62 @@ impl ColumnarWriter {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn record_bool(&mut self, doc: RowId, column_name: &str, val: bool) {
|
||||
pub fn record_ip_addr(&mut self, doc: RowId, column_name: &str, ip_addr: Ipv6Addr) {
|
||||
assert!(
|
||||
!column_name.as_bytes().contains(&0u8),
|
||||
"key may not contain the 0 byte"
|
||||
);
|
||||
let (hash_map, arena) = (&mut self.bool_field_hash_map, &mut self.arena);
|
||||
let (hash_map, arena) = (&mut self.ip_addr_field_hash_map, &mut self.arena);
|
||||
hash_map.mutate_or_create(
|
||||
column_name.as_bytes(),
|
||||
|column_opt: Option<ColumnWriter>| {
|
||||
let mut column: ColumnWriter = column_opt.unwrap_or_default();
|
||||
column.record(doc, val, arena);
|
||||
column.record(doc, ip_addr, arena);
|
||||
column
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn record_bool(&mut self, doc: RowId, column_name: &str, val: bool) {
|
||||
let (hash_map, arena) = (&mut self.bool_field_hash_map, &mut self.arena);
|
||||
mutate_or_create_column(hash_map, column_name, |column_opt: Option<ColumnWriter>| {
|
||||
let mut column: ColumnWriter = column_opt.unwrap_or_default();
|
||||
column.record(doc, val, arena);
|
||||
column
|
||||
});
|
||||
}
|
||||
|
||||
pub fn record_datetime(&mut self, doc: RowId, column_name: &str, datetime: crate::DateTime) {
|
||||
let (hash_map, arena) = (&mut self.datetime_field_hash_map, &mut self.arena);
|
||||
mutate_or_create_column(hash_map, column_name, |column_opt: Option<ColumnWriter>| {
|
||||
let mut column: ColumnWriter = column_opt.unwrap_or_default();
|
||||
column.record(doc, NumericalValue::I64(datetime.timestamp_micros), arena);
|
||||
column
|
||||
});
|
||||
}
|
||||
|
||||
pub fn record_str(&mut self, doc: RowId, column_name: &str, value: &str) {
|
||||
let (hash_map, arena, dictionaries) = (
|
||||
&mut self.str_field_hash_map,
|
||||
&mut self.arena,
|
||||
&mut self.dictionaries,
|
||||
);
|
||||
hash_map.mutate_or_create(
|
||||
column_name.as_bytes(),
|
||||
|column_opt: Option<StrOrBytesColumnWriter>| {
|
||||
let mut column: StrOrBytesColumnWriter = column_opt.unwrap_or_else(|| {
|
||||
// Each column has its own dictionary
|
||||
let dictionary_id = dictionaries.len() as u32;
|
||||
dictionaries.push(DictionaryBuilder::default());
|
||||
StrOrBytesColumnWriter::with_dictionary_id(dictionary_id)
|
||||
});
|
||||
column.record_bytes(doc, value.as_bytes(), dictionaries, arena);
|
||||
column
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn record_bytes(&mut self, doc: RowId, column_name: &str, value: &[u8]) {
|
||||
assert!(
|
||||
!column_name.as_bytes().contains(&0u8),
|
||||
"key may not contain the 0 byte"
|
||||
@@ -118,43 +262,67 @@ impl ColumnarWriter {
|
||||
);
|
||||
hash_map.mutate_or_create(
|
||||
column_name.as_bytes(),
|
||||
|column_opt: Option<StrColumnWriter>| {
|
||||
let mut column: StrColumnWriter = column_opt.unwrap_or_else(|| {
|
||||
|column_opt: Option<StrOrBytesColumnWriter>| {
|
||||
let mut column: StrOrBytesColumnWriter = column_opt.unwrap_or_else(|| {
|
||||
// Each column has its own dictionary
|
||||
let dictionary_id = dictionaries.len() as u32;
|
||||
dictionaries.push(DictionaryBuilder::default());
|
||||
StrColumnWriter::with_dictionary_id(dictionary_id)
|
||||
StrOrBytesColumnWriter::with_dictionary_id(dictionary_id)
|
||||
});
|
||||
column.record_bytes(doc, value.as_bytes(), dictionaries, arena);
|
||||
column.record_bytes(doc, value, dictionaries, arena);
|
||||
column
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
pub fn serialize(&mut self, num_docs: RowId, wrt: &mut dyn io::Write) -> io::Result<()> {
|
||||
let mut serializer = ColumnarSerializer::new(wrt);
|
||||
let mut field_columns: Vec<(&[u8], ColumnTypeCategory, Addr)> = self
|
||||
let mut columns: Vec<(&[u8], ColumnType, Addr)> = self
|
||||
.numerical_field_hash_map
|
||||
.iter()
|
||||
.map(|(term, addr, _)| (term, ColumnTypeCategory::Numerical, addr))
|
||||
.map(|(column_name, addr, _)| {
|
||||
let numerical_column_writer: NumericalColumnWriter =
|
||||
self.numerical_field_hash_map.read(addr);
|
||||
let column_type = numerical_column_writer.numerical_type().into();
|
||||
(column_name, column_type, addr)
|
||||
})
|
||||
.collect();
|
||||
field_columns.extend(
|
||||
columns.extend(
|
||||
self.bytes_field_hash_map
|
||||
.iter()
|
||||
.map(|(term, addr, _)| (term, ColumnTypeCategory::Str, addr)),
|
||||
.map(|(term, addr, _)| (term, ColumnType::Bytes, addr)),
|
||||
);
|
||||
field_columns.extend(
|
||||
columns.extend(
|
||||
self.str_field_hash_map
|
||||
.iter()
|
||||
.map(|(column_name, addr, _)| (column_name, ColumnType::Str, addr)),
|
||||
);
|
||||
columns.extend(
|
||||
self.bool_field_hash_map
|
||||
.iter()
|
||||
.map(|(term, addr, _)| (term, ColumnTypeCategory::Bool, addr)),
|
||||
.map(|(column_name, addr, _)| (column_name, ColumnType::Bool, addr)),
|
||||
);
|
||||
field_columns.sort_unstable_by_key(|(column_name, col_type, _)| (*column_name, *col_type));
|
||||
columns.extend(
|
||||
self.ip_addr_field_hash_map
|
||||
.iter()
|
||||
.map(|(column_name, addr, _)| (column_name, ColumnType::IpAddr, addr)),
|
||||
);
|
||||
columns.extend(
|
||||
self.datetime_field_hash_map
|
||||
.iter()
|
||||
.map(|(column_name, addr, _)| (column_name, ColumnType::DateTime, addr)),
|
||||
);
|
||||
columns.sort_unstable_by_key(|(column_name, col_type, _)| (*column_name, *col_type));
|
||||
|
||||
let (arena, buffers, dictionaries) = (&self.arena, &mut self.buffers, &self.dictionaries);
|
||||
let mut symbol_byte_buffer: Vec<u8> = Vec::new();
|
||||
for (column_name, bytes_or_numerical, addr) in field_columns {
|
||||
match bytes_or_numerical {
|
||||
ColumnTypeCategory::Bool => {
|
||||
let column_writer: ColumnWriter = self.bool_field_hash_map.read(addr);
|
||||
for (column_name, column_type, addr) in columns {
|
||||
match column_type {
|
||||
ColumnType::Bool | ColumnType::DateTime => {
|
||||
let column_writer: ColumnWriter = if column_type == ColumnType::Bool {
|
||||
self.bool_field_hash_map.read(addr)
|
||||
} else {
|
||||
self.datetime_field_hash_map.read(addr)
|
||||
};
|
||||
let cardinality = column_writer.get_cardinality(num_docs);
|
||||
let mut column_serializer =
|
||||
serializer.serialize_column(column_name, ColumnType::Bool);
|
||||
@@ -166,29 +334,50 @@ impl ColumnarWriter {
|
||||
&mut column_serializer,
|
||||
)?;
|
||||
}
|
||||
ColumnTypeCategory::Str => {
|
||||
let str_column_writer: StrColumnWriter = self.bytes_field_hash_map.read(addr);
|
||||
let dictionary_builder =
|
||||
&dictionaries[str_column_writer.dictionary_id as usize];
|
||||
let cardinality = str_column_writer.column_writer.get_cardinality(num_docs);
|
||||
ColumnType::IpAddr => {
|
||||
let column_writer: ColumnWriter = self.ip_addr_field_hash_map.read(addr);
|
||||
let cardinality = column_writer.get_cardinality(num_docs);
|
||||
let mut column_serializer =
|
||||
serializer.serialize_column(column_name, ColumnType::Bytes);
|
||||
serialize_bytes_column(
|
||||
serializer.serialize_column(column_name, ColumnType::IpAddr);
|
||||
serialize_ip_addr_column(
|
||||
cardinality,
|
||||
num_docs,
|
||||
dictionary_builder,
|
||||
str_column_writer.operation_iterator(arena, &mut symbol_byte_buffer),
|
||||
column_writer.operation_iterator(arena, &mut symbol_byte_buffer),
|
||||
buffers,
|
||||
&mut column_serializer,
|
||||
)?;
|
||||
}
|
||||
ColumnTypeCategory::Numerical => {
|
||||
ColumnType::Bytes | ColumnType::Str => {
|
||||
let str_or_bytes_column_writer: StrOrBytesColumnWriter =
|
||||
if column_type == ColumnType::Bytes {
|
||||
self.bytes_field_hash_map.read(addr)
|
||||
} else {
|
||||
self.str_field_hash_map.read(addr)
|
||||
};
|
||||
let dictionary_builder =
|
||||
&dictionaries[str_or_bytes_column_writer.dictionary_id as usize];
|
||||
let cardinality = str_or_bytes_column_writer
|
||||
.column_writer
|
||||
.get_cardinality(num_docs);
|
||||
let mut column_serializer =
|
||||
serializer.serialize_column(column_name, column_type);
|
||||
serialize_bytes_or_str_column(
|
||||
cardinality,
|
||||
num_docs,
|
||||
dictionary_builder,
|
||||
str_or_bytes_column_writer
|
||||
.operation_iterator(arena, &mut symbol_byte_buffer),
|
||||
buffers,
|
||||
&mut column_serializer,
|
||||
)?;
|
||||
}
|
||||
ColumnType::I64 | ColumnType::F64 | ColumnType::U64 => {
|
||||
let numerical_column_writer: NumericalColumnWriter =
|
||||
self.numerical_field_hash_map.read(addr);
|
||||
let (numerical_type, cardinality) =
|
||||
numerical_column_writer.column_type_and_cardinality(num_docs);
|
||||
let mut column_serializer = serializer
|
||||
.serialize_column(column_name, ColumnType::Numerical(numerical_type));
|
||||
let numerical_type = column_type.numerical_type().unwrap();
|
||||
let cardinality = numerical_column_writer.cardinality(num_docs);
|
||||
let mut column_serializer =
|
||||
serializer.serialize_column(column_name, ColumnType::from(numerical_type));
|
||||
serialize_numerical_column(
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -205,7 +394,7 @@ impl ColumnarWriter {
|
||||
}
|
||||
}
|
||||
|
||||
fn serialize_bytes_column(
|
||||
fn serialize_bytes_or_str_column(
|
||||
cardinality: Cardinality,
|
||||
num_docs: RowId,
|
||||
dictionary_builder: &DictionaryBuilder,
|
||||
@@ -232,7 +421,7 @@ fn serialize_bytes_column(
|
||||
ColumnOperation::NewDoc(doc) => ColumnOperation::NewDoc(doc),
|
||||
}
|
||||
});
|
||||
serialize_column(
|
||||
send_to_serialize_column_mappable_to_u64(
|
||||
operation_iterator,
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -261,7 +450,7 @@ fn serialize_numerical_column(
|
||||
} = buffers;
|
||||
match numerical_type {
|
||||
NumericalType::I64 => {
|
||||
serialize_column(
|
||||
send_to_serialize_column_mappable_to_u64(
|
||||
coerce_numerical_symbol::<i64>(op_iterator),
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -271,7 +460,7 @@ fn serialize_numerical_column(
|
||||
)?;
|
||||
}
|
||||
NumericalType::U64 => {
|
||||
serialize_column(
|
||||
send_to_serialize_column_mappable_to_u64(
|
||||
coerce_numerical_symbol::<u64>(op_iterator),
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -281,7 +470,7 @@ fn serialize_numerical_column(
|
||||
)?;
|
||||
}
|
||||
NumericalType::F64 => {
|
||||
serialize_column(
|
||||
send_to_serialize_column_mappable_to_u64(
|
||||
coerce_numerical_symbol::<f64>(op_iterator),
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -306,7 +495,7 @@ fn serialize_bool_column(
|
||||
bool_values,
|
||||
..
|
||||
} = buffers;
|
||||
serialize_column(
|
||||
send_to_serialize_column_mappable_to_u64(
|
||||
column_operations_it,
|
||||
cardinality,
|
||||
num_docs,
|
||||
@@ -317,7 +506,76 @@ fn serialize_bool_column(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn serialize_column<
|
||||
fn serialize_ip_addr_column(
|
||||
cardinality: Cardinality,
|
||||
num_docs: RowId,
|
||||
column_operations_it: impl Iterator<Item = ColumnOperation<Ipv6Addr>>,
|
||||
buffers: &mut SpareBuffers,
|
||||
wrt: &mut impl io::Write,
|
||||
) -> io::Result<()> {
|
||||
let SpareBuffers {
|
||||
value_index_builders,
|
||||
ip_addr_values,
|
||||
..
|
||||
} = buffers;
|
||||
send_to_serialize_column_mappable_to_u128(
|
||||
column_operations_it,
|
||||
cardinality,
|
||||
num_docs,
|
||||
value_index_builders,
|
||||
ip_addr_values,
|
||||
wrt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_to_serialize_column_mappable_to_u128<
|
||||
T: Copy + std::fmt::Debug + Send + Sync + MonotonicallyMappableToU128 + PartialOrd,
|
||||
>(
|
||||
op_iterator: impl Iterator<Item = ColumnOperation<T>>,
|
||||
cardinality: Cardinality,
|
||||
num_docs: RowId,
|
||||
value_index_builders: &mut PreallocatedIndexBuilders,
|
||||
values: &mut Vec<T>,
|
||||
mut wrt: impl io::Write,
|
||||
) -> io::Result<()>
|
||||
where
|
||||
for<'a> VecColumn<'a, T>: ColumnValues<T>,
|
||||
{
|
||||
values.clear();
|
||||
// TODO: split index and values
|
||||
let serializable_column_index = match cardinality {
|
||||
Cardinality::Full => {
|
||||
consume_operation_iterator(
|
||||
op_iterator,
|
||||
value_index_builders.borrow_required_index_builder(),
|
||||
values,
|
||||
);
|
||||
SerializableColumnIndex::Full
|
||||
}
|
||||
Cardinality::Optional => {
|
||||
let optional_index_builder = value_index_builders.borrow_optional_index_builder();
|
||||
consume_operation_iterator(op_iterator, optional_index_builder, values);
|
||||
let optional_index = optional_index_builder.finish(num_docs);
|
||||
SerializableColumnIndex::Optional(Box::new(optional_index))
|
||||
}
|
||||
Cardinality::Multivalued => {
|
||||
let multivalued_index_builder = value_index_builders.borrow_multivalued_index_builder();
|
||||
consume_operation_iterator(op_iterator, multivalued_index_builder, values);
|
||||
let multivalued_index = multivalued_index_builder.finish(num_docs);
|
||||
SerializableColumnIndex::Multivalued(Box::new(multivalued_index))
|
||||
}
|
||||
};
|
||||
crate::column::serialize_column_mappable_to_u128(
|
||||
serializable_column_index,
|
||||
|| values.iter().cloned(),
|
||||
values.len() as u32,
|
||||
&mut wrt,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn send_to_serialize_column_mappable_to_u64<
|
||||
T: Copy + Default + std::fmt::Debug + Send + Sync + MonotonicallyMappableToU64 + PartialOrd,
|
||||
>(
|
||||
op_iterator: impl Iterator<Item = ColumnOperation<T>>,
|
||||
@@ -350,11 +608,10 @@ where
|
||||
let multivalued_index_builder = value_index_builders.borrow_multivalued_index_builder();
|
||||
consume_operation_iterator(op_iterator, multivalued_index_builder, values);
|
||||
let multivalued_index = multivalued_index_builder.finish(num_docs);
|
||||
todo!();
|
||||
// SerializableColumnIndex::Multivalued(Box::new(multivalued_index))
|
||||
SerializableColumnIndex::Multivalued(Box::new(multivalued_index))
|
||||
}
|
||||
};
|
||||
crate::column::serialize_column_u64(
|
||||
crate::column::serialize_column_mappable_to_u64(
|
||||
serializable_column_index,
|
||||
&VecColumn::from(&values[..]),
|
||||
&mut wrt,
|
||||
@@ -392,59 +649,12 @@ fn consume_operation_iterator<T: std::fmt::Debug, TIndexBuilder: IndexBuilder>(
|
||||
}
|
||||
}
|
||||
|
||||
// /// Serializes the column with the codec with the best estimate on the data.
|
||||
// fn serialize_numerical<T: MonotonicallyMappableToU64>(
|
||||
// value_index: ValueIndexInfo,
|
||||
// typed_column: impl Column<T>,
|
||||
// output: &mut impl io::Write,
|
||||
// codecs: &[FastFieldCodecType],
|
||||
// ) -> io::Result<()> {
|
||||
|
||||
// let counting_writer = CountingWriter::wrap(output);
|
||||
// serialize_value_index(value_index, output)?;
|
||||
// let value_index_len = counting_writer.written_bytes();
|
||||
// let output = counting_writer.finish();
|
||||
|
||||
// serialize_column(value_index, output)?;
|
||||
// let column = monotonic_map_column(
|
||||
// typed_column,
|
||||
// crate::column::monotonic_mapping::StrictlyMonotonicMappingToInternal::<T>::new(),
|
||||
// );
|
||||
// let header = Header::compute_header(&column, codecs).ok_or_else(|| {
|
||||
// io::Error::new(
|
||||
// io::ErrorKind::InvalidInput,
|
||||
// format!(
|
||||
// "Data cannot be serialized with this list of codec. {:?}",
|
||||
// codecs
|
||||
// ),
|
||||
// )
|
||||
// })?;
|
||||
// header.serialize(output)?;
|
||||
// let normalized_column = header.normalize_column(column);
|
||||
// assert_eq!(normalized_column.min_value(), 0u64);
|
||||
// serialize_given_codec(normalized_column, header.codec_type, output)?;
|
||||
|
||||
// let column_header = ColumnFooter {
|
||||
// value_index_len: todo!(),
|
||||
// cardinality: todo!(),
|
||||
// };
|
||||
|
||||
// let null_index_footer = NullIndexFooter {
|
||||
// cardinality: value_index.get_cardinality(),
|
||||
// null_index_codec: NullIndexCodec::Full,
|
||||
// null_index_byte_range: 0..0,
|
||||
// };
|
||||
// append_null_index_footer(output, null_index_footer)?;
|
||||
// Ok(())
|
||||
// }
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use column_operation::ColumnOperation;
|
||||
use stacker::MemoryArena;
|
||||
|
||||
use super::*;
|
||||
use crate::value::NumericalValue;
|
||||
use crate::columnar::writer::column_operation::ColumnOperation;
|
||||
use crate::{Cardinality, NumericalValue};
|
||||
|
||||
#[test]
|
||||
fn test_column_writer_required_simple() {
|
||||
|
||||
@@ -97,10 +97,10 @@ mod tests {
|
||||
#[test]
|
||||
fn test_prepare_key_bytes() {
|
||||
let mut buffer: Vec<u8> = b"somegarbage".to_vec();
|
||||
prepare_key(b"root\0child", ColumnType::Bytes, &mut buffer);
|
||||
prepare_key(b"root\0child", ColumnType::Str, &mut buffer);
|
||||
assert_eq!(buffer.len(), 12);
|
||||
assert_eq!(&buffer[..10], b"root\0child");
|
||||
assert_eq!(buffer[10], 0u8);
|
||||
assert_eq!(buffer[11], ColumnType::Bytes.to_code());
|
||||
assert_eq!(buffer[11], ColumnType::Str.to_code());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,16 +45,6 @@ impl<'a> SerializableOptionalIndex<'a> for SingleValueArrayIndex<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionalIndexBuilder {
|
||||
fn num_non_nulls(&self) -> u32 {
|
||||
self.docs.len() as u32
|
||||
}
|
||||
|
||||
fn iter(&self) -> Box<dyn Iterator<Item = u32> + '_> {
|
||||
Box::new(self.docs.iter().copied())
|
||||
}
|
||||
}
|
||||
|
||||
impl OptionalIndexBuilder {
|
||||
pub fn finish<'a>(&'a mut self, num_rows: RowId) -> impl SerializableOptionalIndex + 'a {
|
||||
debug_assert!(self
|
||||
@@ -96,7 +86,7 @@ pub struct MultivaluedIndexBuilder {
|
||||
impl MultivaluedIndexBuilder {
|
||||
pub fn finish(&mut self, num_docs: RowId) -> impl ColumnValues<u32> + '_ {
|
||||
self.start_offsets
|
||||
.resize(num_docs as usize, self.total_num_vals_seen);
|
||||
.resize(num_docs as usize + 1, self.total_num_vals_seen);
|
||||
VecColumn {
|
||||
values: &&self.start_offsets[..],
|
||||
min_value: 0,
|
||||
@@ -188,7 +178,7 @@ mod tests {
|
||||
.finish(4u32)
|
||||
.iter()
|
||||
.collect::<Vec<u32>>(),
|
||||
vec![0, 0, 2, 3]
|
||||
vec![0, 0, 2, 3, 3]
|
||||
);
|
||||
multivalued_value_index_builder.reset();
|
||||
multivalued_value_index_builder.record_row(2u32);
|
||||
@@ -199,7 +189,7 @@ mod tests {
|
||||
.finish(4u32)
|
||||
.iter()
|
||||
.collect::<Vec<u32>>(),
|
||||
vec![0, 0, 0, 2]
|
||||
vec![0, 0, 0, 2, 2]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
use std::io;
|
||||
use std::net::IpAddr;
|
||||
use std::net::Ipv6Addr;
|
||||
use std::sync::Arc;
|
||||
|
||||
use common::file_slice::FileSlice;
|
||||
use common::{HasLen, OwnedBytes};
|
||||
|
||||
use crate::column::{BytesColumn, Column};
|
||||
use crate::column::{BytesColumn, Column, StrColumn};
|
||||
use crate::column_values::{monotonic_map_column, StrictlyMonotonicFn};
|
||||
use crate::columnar::ColumnType;
|
||||
use crate::DateTime;
|
||||
use crate::{Cardinality, DateTime, NumericalType};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub enum DynamicColumn {
|
||||
@@ -14,41 +16,175 @@ pub enum DynamicColumn {
|
||||
I64(Column<i64>),
|
||||
U64(Column<u64>),
|
||||
F64(Column<f64>),
|
||||
IpAddr(Column<IpAddr>),
|
||||
IpAddr(Column<Ipv6Addr>),
|
||||
DateTime(Column<DateTime>),
|
||||
Str(BytesColumn),
|
||||
Bytes(BytesColumn),
|
||||
Str(StrColumn),
|
||||
}
|
||||
|
||||
impl From<Column<i64>> for DynamicColumn {
|
||||
fn from(column_i64: Column<i64>) -> Self {
|
||||
DynamicColumn::I64(column_i64)
|
||||
impl DynamicColumn {
|
||||
pub fn get_cardinality(&self) -> Cardinality {
|
||||
match self {
|
||||
DynamicColumn::Bool(c) => c.get_cardinality(),
|
||||
DynamicColumn::I64(c) => c.get_cardinality(),
|
||||
DynamicColumn::U64(c) => c.get_cardinality(),
|
||||
DynamicColumn::F64(c) => c.get_cardinality(),
|
||||
DynamicColumn::IpAddr(c) => c.get_cardinality(),
|
||||
DynamicColumn::DateTime(c) => c.get_cardinality(),
|
||||
DynamicColumn::Bytes(c) => c.ords().get_cardinality(),
|
||||
DynamicColumn::Str(c) => c.ords().get_cardinality(),
|
||||
}
|
||||
}
|
||||
pub fn column_type(&self) -> ColumnType {
|
||||
match self {
|
||||
DynamicColumn::Bool(_) => ColumnType::Bool,
|
||||
DynamicColumn::I64(_) => ColumnType::I64,
|
||||
DynamicColumn::U64(_) => ColumnType::U64,
|
||||
DynamicColumn::F64(_) => ColumnType::F64,
|
||||
DynamicColumn::IpAddr(_) => ColumnType::IpAddr,
|
||||
DynamicColumn::DateTime(_) => ColumnType::DateTime,
|
||||
DynamicColumn::Bytes(_) => ColumnType::Bytes,
|
||||
DynamicColumn::Str(_) => ColumnType::Str,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_numerical(&self) -> bool {
|
||||
self.column_type().numerical_type().is_some()
|
||||
}
|
||||
|
||||
pub fn is_f64(&self) -> bool {
|
||||
self.column_type().numerical_type() == Some(NumericalType::F64)
|
||||
}
|
||||
pub fn is_i64(&self) -> bool {
|
||||
self.column_type().numerical_type() == Some(NumericalType::I64)
|
||||
}
|
||||
pub fn is_u64(&self) -> bool {
|
||||
self.column_type().numerical_type() == Some(NumericalType::U64)
|
||||
}
|
||||
|
||||
pub fn coerce_to_f64(self) -> Option<DynamicColumn> {
|
||||
match self {
|
||||
DynamicColumn::I64(column) => Some(DynamicColumn::F64(Column {
|
||||
idx: column.idx,
|
||||
values: Arc::new(monotonic_map_column(column.values, MapI64ToF64)),
|
||||
})),
|
||||
DynamicColumn::U64(column) => Some(DynamicColumn::F64(Column {
|
||||
idx: column.idx,
|
||||
values: Arc::new(monotonic_map_column(column.values, MapU64ToF64)),
|
||||
})),
|
||||
DynamicColumn::F64(_) => Some(self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn coerce_to_i64(self) -> Option<DynamicColumn> {
|
||||
match self {
|
||||
DynamicColumn::U64(column) => {
|
||||
if column.max_value() > i64::MAX as u64 {
|
||||
return None;
|
||||
}
|
||||
Some(DynamicColumn::I64(Column {
|
||||
idx: column.idx,
|
||||
values: Arc::new(monotonic_map_column(column.values, MapU64ToI64)),
|
||||
}))
|
||||
}
|
||||
DynamicColumn::I64(_) => Some(self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
pub fn coerce_to_u64(self) -> Option<DynamicColumn> {
|
||||
match self {
|
||||
DynamicColumn::I64(column) => {
|
||||
if column.min_value() < 0 {
|
||||
return None;
|
||||
}
|
||||
Some(DynamicColumn::U64(Column {
|
||||
idx: column.idx,
|
||||
values: Arc::new(monotonic_map_column(column.values, MapI64ToU64)),
|
||||
}))
|
||||
}
|
||||
DynamicColumn::U64(_) => Some(self),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Column<u64>> for DynamicColumn {
|
||||
fn from(column_u64: Column<u64>) -> Self {
|
||||
DynamicColumn::U64(column_u64)
|
||||
struct MapI64ToF64;
|
||||
impl StrictlyMonotonicFn<i64, f64> for MapI64ToF64 {
|
||||
#[inline(always)]
|
||||
fn mapping(&self, inp: i64) -> f64 {
|
||||
inp as f64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inverse(&self, out: f64) -> i64 {
|
||||
out as i64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Column<f64>> for DynamicColumn {
|
||||
fn from(column_f64: Column<f64>) -> Self {
|
||||
DynamicColumn::F64(column_f64)
|
||||
struct MapU64ToF64;
|
||||
impl StrictlyMonotonicFn<u64, f64> for MapU64ToF64 {
|
||||
#[inline(always)]
|
||||
fn mapping(&self, inp: u64) -> f64 {
|
||||
inp as f64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inverse(&self, out: f64) -> u64 {
|
||||
out as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Column<bool>> for DynamicColumn {
|
||||
fn from(bool_column: Column<bool>) -> Self {
|
||||
DynamicColumn::Bool(bool_column)
|
||||
struct MapU64ToI64;
|
||||
impl StrictlyMonotonicFn<u64, i64> for MapU64ToI64 {
|
||||
#[inline(always)]
|
||||
fn mapping(&self, inp: u64) -> i64 {
|
||||
inp as i64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inverse(&self, out: i64) -> u64 {
|
||||
out as u64
|
||||
}
|
||||
}
|
||||
|
||||
impl From<BytesColumn> for DynamicColumn {
|
||||
fn from(dictionary_encoded_col: BytesColumn) -> Self {
|
||||
DynamicColumn::Str(dictionary_encoded_col)
|
||||
struct MapI64ToU64;
|
||||
impl StrictlyMonotonicFn<i64, u64> for MapI64ToU64 {
|
||||
#[inline(always)]
|
||||
fn mapping(&self, inp: i64) -> u64 {
|
||||
inp as u64
|
||||
}
|
||||
#[inline(always)]
|
||||
fn inverse(&self, out: u64) -> i64 {
|
||||
out as i64
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! static_dynamic_conversions {
|
||||
($typ:ty, $enum_name:ident) => {
|
||||
impl Into<Option<$typ>> for DynamicColumn {
|
||||
fn into(self) -> Option<$typ> {
|
||||
if let DynamicColumn::$enum_name(col) = self {
|
||||
Some(col)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<$typ> for DynamicColumn {
|
||||
fn from(typed_column: $typ) -> Self {
|
||||
DynamicColumn::$enum_name(typed_column)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static_dynamic_conversions!(Column<bool>, Bool);
|
||||
static_dynamic_conversions!(Column<u64>, U64);
|
||||
static_dynamic_conversions!(Column<i64>, I64);
|
||||
static_dynamic_conversions!(Column<f64>, F64);
|
||||
static_dynamic_conversions!(Column<crate::DateTime>, DateTime);
|
||||
static_dynamic_conversions!(StrColumn, Str);
|
||||
static_dynamic_conversions!(BytesColumn, Bytes);
|
||||
static_dynamic_conversions!(Column<Ipv6Addr>, IpAddr);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct DynamicColumnHandle {
|
||||
pub(crate) file_slice: FileSlice,
|
||||
@@ -56,31 +192,53 @@ pub struct DynamicColumnHandle {
|
||||
}
|
||||
|
||||
impl DynamicColumnHandle {
|
||||
// TODO rename load
|
||||
pub fn open(&self) -> io::Result<DynamicColumn> {
|
||||
let column_bytes: OwnedBytes = self.file_slice.read_bytes()?;
|
||||
self.open_internal(column_bytes)
|
||||
}
|
||||
|
||||
// TODO rename load_async
|
||||
pub async fn open_async(&self) -> io::Result<DynamicColumn> {
|
||||
let column_bytes: OwnedBytes = self.file_slice.read_bytes_async().await?;
|
||||
self.open_internal(column_bytes)
|
||||
}
|
||||
|
||||
/// Returns the `u64` fast field reader reader associated with `fields` of types
|
||||
/// Str, u64, i64, f64, or datetime.
|
||||
///
|
||||
/// If not, the fastfield reader will returns the u64-value associated with the original
|
||||
/// FastValue.
|
||||
pub fn open_u64_lenient(&self) -> io::Result<Option<Column<u64>>> {
|
||||
let column_bytes = self.file_slice.read_bytes()?;
|
||||
match self.column_type {
|
||||
ColumnType::Str | ColumnType::Bytes => {
|
||||
let column: BytesColumn = crate::column::open_column_bytes(column_bytes)?;
|
||||
Ok(Some(column.term_ord_column))
|
||||
}
|
||||
ColumnType::Bool => Ok(None),
|
||||
ColumnType::IpAddr => Ok(None),
|
||||
ColumnType::I64 | ColumnType::U64 | ColumnType::F64 | ColumnType::DateTime => {
|
||||
let column = crate::column::open_column_u64::<u64>(column_bytes)?;
|
||||
Ok(Some(column))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn open_internal(&self, column_bytes: OwnedBytes) -> io::Result<DynamicColumn> {
|
||||
let dynamic_column: DynamicColumn = match self.column_type {
|
||||
ColumnType::Bytes => crate::column::open_column_bytes(column_bytes)?.into(),
|
||||
ColumnType::Numerical(numerical_type) => match numerical_type {
|
||||
crate::NumericalType::I64 => {
|
||||
crate::column::open_column_u64::<i64>(column_bytes)?.into()
|
||||
}
|
||||
crate::NumericalType::U64 => {
|
||||
crate::column::open_column_u64::<u64>(column_bytes)?.into()
|
||||
}
|
||||
crate::NumericalType::F64 => {
|
||||
crate::column::open_column_u64::<f64>(column_bytes)?.into()
|
||||
}
|
||||
},
|
||||
ColumnType::Bytes => {
|
||||
crate::column::open_column_bytes::<BytesColumn>(column_bytes)?.into()
|
||||
}
|
||||
ColumnType::Str => crate::column::open_column_bytes::<StrColumn>(column_bytes)?.into(),
|
||||
ColumnType::I64 => crate::column::open_column_u64::<i64>(column_bytes)?.into(),
|
||||
ColumnType::U64 => crate::column::open_column_u64::<u64>(column_bytes)?.into(),
|
||||
ColumnType::F64 => crate::column::open_column_u64::<f64>(column_bytes)?.into(),
|
||||
ColumnType::Bool => crate::column::open_column_u64::<bool>(column_bytes)?.into(),
|
||||
ColumnType::IpAddr => crate::column::open_column_u128::<Ipv6Addr>(column_bytes)?.into(),
|
||||
ColumnType::DateTime => {
|
||||
crate::column::open_column_u64::<crate::DateTime>(column_bytes)?.into()
|
||||
}
|
||||
};
|
||||
Ok(dynamic_column)
|
||||
}
|
||||
|
||||
@@ -18,16 +18,21 @@ mod dynamic_column;
|
||||
pub(crate) mod utils;
|
||||
mod value;
|
||||
|
||||
pub use columnar::{ColumnarReader, ColumnarWriter};
|
||||
pub use column::{BytesColumn, Column, StrColumn};
|
||||
pub use column_values::ColumnValues;
|
||||
pub use columnar::{
|
||||
merge_columnar, ColumnType, ColumnarReader, ColumnarWriter, HasAssociatedColumnType,
|
||||
MergeDocOrder,
|
||||
};
|
||||
pub use value::{NumericalType, NumericalValue};
|
||||
|
||||
// pub use self::dynamic_column::DynamicColumnHandle;
|
||||
pub use self::dynamic_column::{DynamicColumn, DynamicColumnHandle};
|
||||
|
||||
pub type RowId = u32;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, PartialOrd, PartialEq, Default, Debug)]
|
||||
pub struct DateTime {
|
||||
timestamp_micros: i64,
|
||||
pub timestamp_micros: i64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
@@ -57,6 +62,12 @@ pub enum Cardinality {
|
||||
}
|
||||
|
||||
impl Cardinality {
|
||||
pub fn is_optional(&self) -> bool {
|
||||
matches!(self, Cardinality::Optional)
|
||||
}
|
||||
pub fn is_multivalue(&self) -> bool {
|
||||
matches!(self, Cardinality::Multivalued)
|
||||
}
|
||||
pub(crate) fn to_code(self) -> u8 {
|
||||
self as u8
|
||||
}
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
use std::net::Ipv6Addr;
|
||||
|
||||
use crate::column_values::MonotonicallyMappableToU128;
|
||||
use crate::columnar::ColumnType;
|
||||
use crate::dynamic_column::{DynamicColumn, DynamicColumnHandle};
|
||||
use crate::value::NumericalValue;
|
||||
use crate::{Cardinality, ColumnarReader, ColumnarWriter};
|
||||
|
||||
#[test]
|
||||
fn test_dataframe_writer_bytes() {
|
||||
fn test_dataframe_writer_str() {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_str(1u32, "my_string", "hello");
|
||||
dataframe_writer.record_str(3u32, "my_string", "helloeee");
|
||||
@@ -14,7 +17,21 @@ fn test_dataframe_writer_bytes() {
|
||||
assert_eq!(columnar.num_columns(), 1);
|
||||
let cols: Vec<DynamicColumnHandle> = columnar.read_columns("my_string").unwrap();
|
||||
assert_eq!(cols.len(), 1);
|
||||
assert_eq!(cols[0].num_bytes(), 165);
|
||||
assert_eq!(cols[0].num_bytes(), 158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dataframe_writer_bytes() {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_bytes(1u32, "my_string", b"hello");
|
||||
dataframe_writer.record_bytes(3u32, "my_string", b"helloeee");
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(5, &mut buffer).unwrap();
|
||||
let columnar = ColumnarReader::open(buffer).unwrap();
|
||||
assert_eq!(columnar.num_columns(), 1);
|
||||
let cols: Vec<DynamicColumnHandle> = columnar.read_columns("my_string").unwrap();
|
||||
assert_eq!(cols.len(), 1);
|
||||
assert_eq!(cols[0].num_bytes(), 158);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -28,7 +45,7 @@ fn test_dataframe_writer_bool() {
|
||||
assert_eq!(columnar.num_columns(), 1);
|
||||
let cols: Vec<DynamicColumnHandle> = columnar.read_columns("bool.value").unwrap();
|
||||
assert_eq!(cols.len(), 1);
|
||||
assert_eq!(cols[0].num_bytes(), 29);
|
||||
assert_eq!(cols[0].num_bytes(), 22);
|
||||
assert_eq!(cols[0].column_type(), ColumnType::Bool);
|
||||
let dyn_bool_col = cols[0].open().unwrap();
|
||||
let DynamicColumn::Bool(bool_col) = dyn_bool_col else { panic!(); };
|
||||
@@ -36,6 +53,59 @@ fn test_dataframe_writer_bool() {
|
||||
assert_eq!(&vals, &[None, Some(false), None, Some(true), None,]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dataframe_writer_u64_multivalued() {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_numerical(2u32, "divisor", 2u64);
|
||||
dataframe_writer.record_numerical(3u32, "divisor", 3u64);
|
||||
dataframe_writer.record_numerical(4u32, "divisor", 2u64);
|
||||
dataframe_writer.record_numerical(5u32, "divisor", 5u64);
|
||||
dataframe_writer.record_numerical(6u32, "divisor", 2u64);
|
||||
dataframe_writer.record_numerical(6u32, "divisor", 3u64);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(7, &mut buffer).unwrap();
|
||||
let columnar = ColumnarReader::open(buffer).unwrap();
|
||||
assert_eq!(columnar.num_columns(), 1);
|
||||
let cols: Vec<DynamicColumnHandle> = columnar.read_columns("divisor").unwrap();
|
||||
assert_eq!(cols.len(), 1);
|
||||
assert_eq!(cols[0].num_bytes(), 29);
|
||||
let dyn_i64_col = cols[0].open().unwrap();
|
||||
let DynamicColumn::I64(divisor_col) = dyn_i64_col else { panic!(); };
|
||||
assert_eq!(
|
||||
divisor_col.get_cardinality(),
|
||||
crate::Cardinality::Multivalued
|
||||
);
|
||||
assert_eq!(divisor_col.num_rows(), 7);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dataframe_writer_ip_addr() {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
dataframe_writer.record_ip_addr(1, "ip_addr", Ipv6Addr::from_u128(1001));
|
||||
dataframe_writer.record_ip_addr(3, "ip_addr", Ipv6Addr::from_u128(1050));
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
dataframe_writer.serialize(5, &mut buffer).unwrap();
|
||||
let columnar = ColumnarReader::open(buffer).unwrap();
|
||||
assert_eq!(columnar.num_columns(), 1);
|
||||
let cols: Vec<DynamicColumnHandle> = columnar.read_columns("ip_addr").unwrap();
|
||||
assert_eq!(cols.len(), 1);
|
||||
assert_eq!(cols[0].num_bytes(), 42);
|
||||
assert_eq!(cols[0].column_type(), ColumnType::IpAddr);
|
||||
let dyn_bool_col = cols[0].open().unwrap();
|
||||
let DynamicColumn::IpAddr(ip_col) = dyn_bool_col else { panic!(); };
|
||||
let vals: Vec<Option<Ipv6Addr>> = (0..5).map(|row_id| ip_col.first(row_id)).collect();
|
||||
assert_eq!(
|
||||
&vals,
|
||||
&[
|
||||
None,
|
||||
Some(Ipv6Addr::from_u128(1001)),
|
||||
None,
|
||||
Some(Ipv6Addr::from_u128(1050)),
|
||||
None,
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dataframe_writer_numerical() {
|
||||
let mut dataframe_writer = ColumnarWriter::default();
|
||||
@@ -53,7 +123,7 @@ fn test_dataframe_writer_numerical() {
|
||||
// - header 14 bytes
|
||||
// - vals 8 //< due to padding? could have been 1byte?.
|
||||
// - null footer 6 bytes
|
||||
assert_eq!(cols[0].num_bytes(), 40);
|
||||
assert_eq!(cols[0].num_bytes(), 33);
|
||||
let column = cols[0].open().unwrap();
|
||||
let DynamicColumn::I64(column_i64) = column else { panic!(); };
|
||||
assert_eq!(column_i64.idx.get_cardinality(), Cardinality::Optional);
|
||||
@@ -67,18 +137,76 @@ fn test_dataframe_writer_numerical() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dictionary_encoded() {
|
||||
fn test_dictionary_encoded_str() {
|
||||
let mut buffer = Vec::new();
|
||||
let mut columnar_writer = ColumnarWriter::default();
|
||||
columnar_writer.record_str(1, "my.column", "my.key");
|
||||
columnar_writer.record_str(3, "my.column", "my.key2");
|
||||
columnar_writer.record_str(1, "my.column", "a");
|
||||
columnar_writer.record_str(3, "my.column", "c");
|
||||
columnar_writer.record_str(3, "my.column2", "different_column!");
|
||||
columnar_writer.record_str(4, "my.column", "b");
|
||||
columnar_writer.serialize(5, &mut buffer).unwrap();
|
||||
let columnar_reader = ColumnarReader::open(buffer).unwrap();
|
||||
assert_eq!(columnar_reader.num_columns(), 2);
|
||||
let col_handles = columnar_reader.read_columns("my.column").unwrap();
|
||||
assert_eq!(col_handles.len(), 1);
|
||||
let DynamicColumn::Str(str_col) = col_handles[0].open().unwrap() else { panic!(); };
|
||||
let index: Vec<Option<u64>> = (0..5).map(|row_id| str_col.ords().first(row_id)).collect();
|
||||
assert_eq!(index, &[None, Some(0), None, Some(2), Some(1)]);
|
||||
assert_eq!(str_col.num_rows(), 5);
|
||||
// let term_ords = (0..)
|
||||
let mut term_buffer = String::new();
|
||||
let term_ords = str_col.ords();
|
||||
assert_eq!(term_ords.first(0), None);
|
||||
assert_eq!(term_ords.first(1), Some(0));
|
||||
str_col.ord_to_str(0u64, &mut term_buffer).unwrap();
|
||||
assert_eq!(term_buffer, "a");
|
||||
assert_eq!(term_ords.first(2), None);
|
||||
assert_eq!(term_ords.first(3), Some(2));
|
||||
str_col.ord_to_str(2u64, &mut term_buffer).unwrap();
|
||||
assert_eq!(term_buffer, "c");
|
||||
assert_eq!(term_ords.first(4), Some(1));
|
||||
str_col.ord_to_str(1u64, &mut term_buffer).unwrap();
|
||||
assert_eq!(term_buffer, "b");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_dictionary_encoded_bytes() {
|
||||
let mut buffer = Vec::new();
|
||||
let mut columnar_writer = ColumnarWriter::default();
|
||||
columnar_writer.record_bytes(1, "my.column", b"a");
|
||||
columnar_writer.record_bytes(3, "my.column", b"c");
|
||||
columnar_writer.record_bytes(3, "my.column2", b"different_column!");
|
||||
columnar_writer.record_bytes(4, "my.column", b"b");
|
||||
columnar_writer.serialize(5, &mut buffer).unwrap();
|
||||
let columnar_reader = ColumnarReader::open(buffer).unwrap();
|
||||
assert_eq!(columnar_reader.num_columns(), 2);
|
||||
let col_handles = columnar_reader.read_columns("my.column").unwrap();
|
||||
assert_eq!(col_handles.len(), 1);
|
||||
let DynamicColumn::Bytes(bytes_col) = col_handles[0].open().unwrap() else { panic!(); };
|
||||
let index: Vec<Option<u64>> = (0..5)
|
||||
.map(|row_id| bytes_col.ords().first(row_id))
|
||||
.collect();
|
||||
assert_eq!(index, &[None, Some(0), None, Some(2), Some(1)]);
|
||||
assert_eq!(bytes_col.num_rows(), 5);
|
||||
let mut term_buffer = Vec::new();
|
||||
let term_ords = bytes_col.ords();
|
||||
assert_eq!(term_ords.first(0), None);
|
||||
assert_eq!(term_ords.first(1), Some(0));
|
||||
bytes_col
|
||||
.dictionary
|
||||
.ord_to_term(0u64, &mut term_buffer)
|
||||
.unwrap();
|
||||
assert_eq!(term_buffer, b"a");
|
||||
assert_eq!(term_ords.first(2), None);
|
||||
assert_eq!(term_ords.first(3), Some(2));
|
||||
bytes_col
|
||||
.dictionary
|
||||
.ord_to_term(2u64, &mut term_buffer)
|
||||
.unwrap();
|
||||
assert_eq!(term_buffer, b"c");
|
||||
assert_eq!(term_ords.first(4), Some(1));
|
||||
bytes_col
|
||||
.dictionary
|
||||
.ord_to_term(1u64, &mut term_buffer)
|
||||
.unwrap();
|
||||
assert_eq!(term_buffer, b"b");
|
||||
}
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
use crate::InvalidData;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
#[derive(Copy, Clone, PartialEq, Debug)]
|
||||
pub enum NumericalValue {
|
||||
I64(i64),
|
||||
U64(u64),
|
||||
F64(f64),
|
||||
}
|
||||
|
||||
impl NumericalValue {
|
||||
pub fn numerical_type(&self) -> NumericalType {
|
||||
match self {
|
||||
NumericalValue::I64(_) => NumericalType::I64,
|
||||
NumericalValue::U64(_) => NumericalType::U64,
|
||||
NumericalValue::F64(_) => NumericalType::F64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u64> for NumericalValue {
|
||||
fn from(val: u64) -> NumericalValue {
|
||||
NumericalValue::U64(val)
|
||||
@@ -25,18 +35,6 @@ impl From<f64> for NumericalValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl NumericalValue {
|
||||
pub fn numerical_type(&self) -> NumericalType {
|
||||
match self {
|
||||
NumericalValue::F64(_) => NumericalType::F64,
|
||||
NumericalValue::I64(_) => NumericalType::I64,
|
||||
NumericalValue::U64(_) => NumericalType::U64,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for NumericalValue {}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, Hash, Eq, PartialEq)]
|
||||
#[repr(u8)]
|
||||
pub enum NumericalType {
|
||||
@@ -106,6 +104,13 @@ impl Coerce for f64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Coerce for crate::DateTime {
|
||||
fn coerce(value: NumericalValue) -> Self {
|
||||
let timestamp_micros = i64::coerce(value);
|
||||
crate::DateTime { timestamp_micros }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::NumericalType;
|
||||
|
||||
@@ -402,8 +402,8 @@ mod tests {
|
||||
let mut buffer = Vec::new();
|
||||
let col = VecColumn::from(&[false, true][..]);
|
||||
serialize(col, &mut buffer, &ALL_CODEC_TYPES).unwrap();
|
||||
// 5 bytes of header, 1 byte of value, 7 bytes of padding.
|
||||
assert_eq!(buffer.len(), 3 + 5 + 8 + 4 + 2);
|
||||
// 5 bytes of header, 1 byte of value
|
||||
assert_eq!(buffer.len(), 3 + 5 + 1 + 4 + 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -411,8 +411,8 @@ mod tests {
|
||||
let mut buffer = Vec::new();
|
||||
let col = VecColumn::from(&[true][..]);
|
||||
serialize(col, &mut buffer, &ALL_CODEC_TYPES).unwrap();
|
||||
// 5 bytes of header, 0 bytes of value, 7 bytes of padding.
|
||||
assert_eq!(buffer.len(), 3 + 5 + 7 + 4 + 2);
|
||||
// 5 bytes of header, 0 bytes of value
|
||||
assert_eq!(buffer.len(), 3 + 5 + 4 + 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -422,6 +422,6 @@ mod tests {
|
||||
let col = VecColumn::from(&vals[..]);
|
||||
serialize(col, &mut buffer, &[FastFieldCodecType::Bitpacked]).unwrap();
|
||||
// Values are stored over 3 bits.
|
||||
assert_eq!(buffer.len(), 3 + 7 + (3 * 80 / 8) + 7 + 4 + 2);
|
||||
assert_eq!(buffer.len(), 3 + 7 + (3 * 80 / 8) + 4 + 2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
cargo test
|
||||
@@ -6,14 +6,13 @@ use super::{IntermediateStats, SegmentStatsCollector};
|
||||
|
||||
/// A single-value metric aggregation that computes the average of numeric values that are
|
||||
/// extracted from the aggregated documents.
|
||||
/// Supported field types are u64, i64, and f64.
|
||||
/// See [super::SingleMetricResult] for return value.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "avg": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -6,14 +6,13 @@ use super::{IntermediateStats, SegmentStatsCollector};
|
||||
|
||||
/// A single-value metric aggregation that counts the number of values that are
|
||||
/// extracted from the aggregated documents.
|
||||
/// Supported field types are u64, i64, and f64.
|
||||
/// See [super::SingleMetricResult] for return value.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "value_count": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -6,14 +6,13 @@ use super::{IntermediateStats, SegmentStatsCollector};
|
||||
|
||||
/// A single-value metric aggregation that computes the maximum of numeric values that are
|
||||
/// extracted from the aggregated documents.
|
||||
/// Supported field types are u64, i64, and f64.
|
||||
/// See [super::SingleMetricResult] for return value.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "max": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -6,14 +6,13 @@ use super::{IntermediateStats, SegmentStatsCollector};
|
||||
|
||||
/// A single-value metric aggregation that computes the minimum of numeric values that are
|
||||
/// extracted from the aggregated documents.
|
||||
/// Supported field types are u64, i64, and f64.
|
||||
/// See [super::SingleMetricResult] for return value.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "min": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -7,14 +7,13 @@ use crate::{DocId, TantivyError};
|
||||
|
||||
/// A multi-value metric aggregation that computes a collection of statistics on numeric values that
|
||||
/// are extracted from the aggregated documents.
|
||||
/// Supported field types are `u64`, `i64`, and `f64`.
|
||||
/// See [`Stats`] for returned statistics.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "stats": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -6,14 +6,13 @@ use super::{IntermediateStats, SegmentStatsCollector};
|
||||
|
||||
/// A single-value metric aggregation that sums up numeric values that are
|
||||
/// extracted from the aggregated documents.
|
||||
/// Supported field types are u64, i64, and f64.
|
||||
/// See [super::SingleMetricResult] for return value.
|
||||
///
|
||||
/// # JSON Format
|
||||
/// ```json
|
||||
/// {
|
||||
/// "sum": {
|
||||
/// "field": "score",
|
||||
/// "field": "score"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
//! # Aggregations
|
||||
//!
|
||||
//!
|
||||
//! An aggregation summarizes your data as statistics on buckets or metrics.
|
||||
//!
|
||||
//! Aggregations can provide answer to questions like:
|
||||
@@ -41,6 +40,10 @@
|
||||
//! - [Metric](metric)
|
||||
//! - [Average](metric::AverageAggregation)
|
||||
//! - [Stats](metric::StatsAggregation)
|
||||
//! - [Min](metric::MinAggregation)
|
||||
//! - [Max](metric::MaxAggregation)
|
||||
//! - [Sum](metric::SumAggregation)
|
||||
//! - [Count](metric::CountAggregation)
|
||||
//!
|
||||
//! # Example
|
||||
//! Compute the average metric, by building [`agg_req::Aggregations`], which is built from an
|
||||
@@ -75,7 +78,7 @@
|
||||
//! }
|
||||
//! ```
|
||||
//! # Example JSON
|
||||
//! Requests are compatible with the elasticsearch json request format.
|
||||
//! Requests are compatible with the elasticsearch JSON request format.
|
||||
//!
|
||||
//! ```
|
||||
//! use tantivy::aggregation::agg_req::Aggregations;
|
||||
|
||||
@@ -226,7 +226,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 34);
|
||||
assert_eq!(file.len(), 27);
|
||||
let composite_file = CompositeFile::open(&file)?;
|
||||
let fast_field_bytes = composite_file.open_read(*FIELD).unwrap().read_bytes()?;
|
||||
let fast_field_reader = open::<u64>(fast_field_bytes)?;
|
||||
@@ -275,7 +275,7 @@ mod tests {
|
||||
serializer.close()?;
|
||||
}
|
||||
let file = directory.open_read(path)?;
|
||||
assert_eq!(file.len(), 62);
|
||||
assert_eq!(file.len(), 55);
|
||||
{
|
||||
let fast_fields_composite = CompositeFile::open(&file)?;
|
||||
let data = fast_fields_composite
|
||||
@@ -316,7 +316,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 35);
|
||||
assert_eq!(file.len(), 28);
|
||||
{
|
||||
let fast_fields_composite = CompositeFile::open(&file).unwrap();
|
||||
let data = fast_fields_composite
|
||||
@@ -355,7 +355,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 80049);
|
||||
assert_eq!(file.len(), 80042);
|
||||
{
|
||||
let fast_fields_composite = CompositeFile::open(&file)?;
|
||||
let data = fast_fields_composite
|
||||
@@ -397,7 +397,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 49_usize);
|
||||
assert_eq!(file.len(), 42_usize);
|
||||
|
||||
{
|
||||
let fast_fields_composite = CompositeFile::open(&file)?;
|
||||
@@ -836,7 +836,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 33);
|
||||
assert_eq!(file.len(), 26);
|
||||
let composite_file = CompositeFile::open(&file)?;
|
||||
let data = composite_file.open_read(field).unwrap().read_bytes()?;
|
||||
let fast_field_reader = open::<bool>(data)?;
|
||||
@@ -874,7 +874,7 @@ mod tests {
|
||||
serializer.close().unwrap();
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
assert_eq!(file.len(), 45);
|
||||
assert_eq!(file.len(), 38);
|
||||
let composite_file = CompositeFile::open(&file)?;
|
||||
let data = composite_file.open_read(field).unwrap().read_bytes()?;
|
||||
let fast_field_reader = open::<bool>(data)?;
|
||||
@@ -906,7 +906,7 @@ mod tests {
|
||||
}
|
||||
let file = directory.open_read(path).unwrap();
|
||||
let composite_file = CompositeFile::open(&file)?;
|
||||
assert_eq!(file.len(), 32);
|
||||
assert_eq!(file.len(), 25);
|
||||
let data = composite_file.open_read(field).unwrap().read_bytes()?;
|
||||
let fast_field_reader = open::<bool>(data)?;
|
||||
assert_eq!(fast_field_reader.get_val(0), false);
|
||||
@@ -940,10 +940,10 @@ mod tests {
|
||||
pub fn test_gcd_date() -> crate::Result<()> {
|
||||
let size_prec_sec =
|
||||
test_gcd_date_with_codec(FastFieldCodecType::Bitpacked, DatePrecision::Seconds)?;
|
||||
assert_eq!(size_prec_sec, 5 + 4 + 28 + (1_000 * 13) / 8); // 13 bits per val = ceil(log_2(number of seconds in 2hours);
|
||||
assert_eq!(size_prec_sec, 5 + 4 + 21 + (1_000 * 13) / 8); // 13 bits per val = ceil(log_2(number of seconds in 2hours);
|
||||
let size_prec_micro =
|
||||
test_gcd_date_with_codec(FastFieldCodecType::Bitpacked, DatePrecision::Microseconds)?;
|
||||
assert_eq!(size_prec_micro, 5 + 4 + 26 + (1_000 * 33) / 8); // 33 bits per val = ceil(log_2(number of microsecsseconds in 2hours);
|
||||
assert_eq!(size_prec_micro, 5 + 4 + 19 + (1_000 * 33) / 8); // 33 bits per val = ceil(log_2(number of microsecsseconds in 2hours);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ impl TextOptions {
|
||||
self.stored
|
||||
}
|
||||
|
||||
/// Returns true iff the value is a fast field.
|
||||
/// Returns true if and only if the value is a fast field.
|
||||
pub fn is_fast(&self) -> bool {
|
||||
self.fast
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ mod tests {
|
||||
bitpack.write(51, 6, &mut buffer).unwrap();
|
||||
assert_eq!(compute_num_bits(51), 6);
|
||||
bitpack.close(&mut buffer).unwrap();
|
||||
assert_eq!(buffer.len(), 3 + 7);
|
||||
assert_eq!(buffer.len(), 3);
|
||||
assert_eq!(extract_bits(&buffer[..], 0, 9), 321u64);
|
||||
assert_eq!(extract_bits(&buffer[..], 9, 2), 2u64);
|
||||
assert_eq!(extract_bits(&buffer[..], 11, 6), 51u64);
|
||||
|
||||
@@ -30,6 +30,7 @@ use crate::{BlockAddr, DeltaReader, Reader, SSTable, SSTableIndex, TermOrdinal};
|
||||
/// block boundary.
|
||||
///
|
||||
/// (See also README.md)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dictionary<TSSTable: SSTable> {
|
||||
pub sstable_slice: FileSlice,
|
||||
pub sstable_index: SSTableIndex,
|
||||
@@ -69,14 +70,16 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
pub(crate) fn sstable_delta_reader_for_key_range(
|
||||
&self,
|
||||
key_range: impl RangeBounds<[u8]>,
|
||||
limit: Option<u64>,
|
||||
) -> io::Result<DeltaReader<'static, TSSTable::ValueReader>> {
|
||||
let slice = self.file_slice_for_range(key_range);
|
||||
let slice = self.file_slice_for_range(key_range, limit);
|
||||
let data = slice.read_bytes()?;
|
||||
Ok(TSSTable::delta_reader(data))
|
||||
}
|
||||
|
||||
/// This function returns a file slice covering a set of sstable blocks
|
||||
/// that include the key range passed in arguments.
|
||||
/// that include the key range passed in arguments. Optionally returns
|
||||
/// only block for up to `limit` matching terms.
|
||||
///
|
||||
/// It works by identifying
|
||||
/// - `first_block`: the block containing the start boudary key
|
||||
@@ -92,26 +95,56 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
/// On the rare edge case where a user asks for `(start_key, end_key]`
|
||||
/// and `start_key` happens to be the last key of a block, we return a
|
||||
/// slice that is the first block was not necessary.
|
||||
pub fn file_slice_for_range(&self, key_range: impl RangeBounds<[u8]>) -> FileSlice {
|
||||
let start_bound: Bound<usize> = match key_range.start_bound() {
|
||||
pub fn file_slice_for_range(
|
||||
&self,
|
||||
key_range: impl RangeBounds<[u8]>,
|
||||
limit: Option<u64>,
|
||||
) -> FileSlice {
|
||||
let first_block_id = match key_range.start_bound() {
|
||||
Bound::Included(key) | Bound::Excluded(key) => {
|
||||
let Some(first_block_addr) = self.sstable_index.search_block(key) else {
|
||||
let Some(first_block_id) = self.sstable_index.locate_with_key(key) else {
|
||||
return FileSlice::empty();
|
||||
};
|
||||
Bound::Included(first_block_addr.byte_range.start)
|
||||
Some(first_block_id)
|
||||
}
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
Bound::Unbounded => None,
|
||||
};
|
||||
let end_bound: Bound<usize> = match key_range.end_bound() {
|
||||
Bound::Included(key) | Bound::Excluded(key) => {
|
||||
if let Some(block_addr) = self.sstable_index.search_block(key) {
|
||||
Bound::Excluded(block_addr.byte_range.end)
|
||||
|
||||
let last_block_id = match key_range.end_bound() {
|
||||
Bound::Included(key) | Bound::Excluded(key) => self.sstable_index.locate_with_key(key),
|
||||
Bound::Unbounded => None,
|
||||
};
|
||||
|
||||
let start_bound = if let Some(first_block_id) = first_block_id {
|
||||
let Some(block_addr) = self.sstable_index.get_block(first_block_id) else {
|
||||
return FileSlice::empty();
|
||||
};
|
||||
Bound::Included(block_addr.byte_range.start)
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
};
|
||||
|
||||
let last_block_id = if let Some(limit) = limit {
|
||||
let second_block_id = first_block_id.map(|id| id + 1).unwrap_or(0);
|
||||
if let Some(block_addr) = self.sstable_index.get_block(second_block_id) {
|
||||
let ordinal_limit = block_addr.first_ordinal + limit;
|
||||
let last_block_limit = self.sstable_index.locate_with_ord(ordinal_limit);
|
||||
if let Some(last_block_id) = last_block_id {
|
||||
Some(last_block_id.min(last_block_limit))
|
||||
} else {
|
||||
Bound::Unbounded
|
||||
Some(last_block_limit)
|
||||
}
|
||||
} else {
|
||||
last_block_id
|
||||
}
|
||||
Bound::Unbounded => Bound::Unbounded,
|
||||
} else {
|
||||
last_block_id
|
||||
};
|
||||
let end_bound = last_block_id
|
||||
.and_then(|block_id| self.sstable_index.get_block(block_id))
|
||||
.map(|block_addr| Bound::Excluded(block_addr.byte_range.end))
|
||||
.unwrap_or(Bound::Unbounded);
|
||||
|
||||
self.sstable_slice.slice((start_bound, end_bound))
|
||||
}
|
||||
|
||||
@@ -156,10 +189,15 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
|
||||
/// Returns the ordinal associated with a given term.
|
||||
pub fn term_ord<K: AsRef<[u8]>>(&self, key: K) -> io::Result<Option<TermOrdinal>> {
|
||||
let mut term_ord = 0u64;
|
||||
let key_bytes = key.as_ref();
|
||||
let mut sstable_reader = self.sstable_reader()?;
|
||||
while sstable_reader.advance().unwrap_or(false) {
|
||||
|
||||
let Some(block_addr) = self.sstable_index.get_block_with_key(key_bytes) else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let mut term_ord = block_addr.first_ordinal;
|
||||
let mut sstable_reader = self.sstable_reader_block(block_addr)?;
|
||||
while sstable_reader.advance()? {
|
||||
if sstable_reader.key() == key_bytes {
|
||||
return Ok(Some(term_ord));
|
||||
}
|
||||
@@ -178,22 +216,32 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
/// Regardless of whether the term is found or not,
|
||||
/// the buffer may be modified.
|
||||
pub fn ord_to_term(&self, ord: TermOrdinal, bytes: &mut Vec<u8>) -> io::Result<bool> {
|
||||
let mut sstable_reader = self.sstable_reader()?;
|
||||
bytes.clear();
|
||||
for _ in 0..(ord + 1) {
|
||||
if !sstable_reader.advance().unwrap_or(false) {
|
||||
// find block in which the term would be
|
||||
let block_addr = self.sstable_index.get_block_with_ord(ord);
|
||||
let first_ordinal = block_addr.first_ordinal;
|
||||
|
||||
// then search inside that block only
|
||||
let mut sstable_reader = self.sstable_reader_block(block_addr)?;
|
||||
for _ in first_ordinal..=ord {
|
||||
if !sstable_reader.advance()? {
|
||||
return Ok(false);
|
||||
}
|
||||
}
|
||||
bytes.clear();
|
||||
bytes.extend_from_slice(sstable_reader.key());
|
||||
Ok(true)
|
||||
}
|
||||
|
||||
/// Returns the number of terms in the dictionary.
|
||||
pub fn term_info_from_ord(&self, term_ord: TermOrdinal) -> io::Result<Option<TSSTable::Value>> {
|
||||
let mut sstable_reader = self.sstable_reader()?;
|
||||
for _ in 0..(term_ord + 1) {
|
||||
if !sstable_reader.advance().unwrap_or(false) {
|
||||
// find block in which the term would be
|
||||
let block_addr = self.sstable_index.get_block_with_ord(term_ord);
|
||||
let first_ordinal = block_addr.first_ordinal;
|
||||
|
||||
// then search inside that block only
|
||||
let mut sstable_reader = self.sstable_reader_block(block_addr)?;
|
||||
for _ in first_ordinal..=term_ord {
|
||||
if !sstable_reader.advance()? {
|
||||
return Ok(None);
|
||||
}
|
||||
}
|
||||
@@ -202,10 +250,10 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
|
||||
/// Lookups the value corresponding to the key.
|
||||
pub fn get<K: AsRef<[u8]>>(&self, key: K) -> io::Result<Option<TSSTable::Value>> {
|
||||
if let Some(block_addr) = self.sstable_index.search_block(key.as_ref()) {
|
||||
if let Some(block_addr) = self.sstable_index.get_block_with_key(key.as_ref()) {
|
||||
let mut sstable_reader = self.sstable_reader_block(block_addr)?;
|
||||
let key_bytes = key.as_ref();
|
||||
while sstable_reader.advance().unwrap_or(false) {
|
||||
while sstable_reader.advance()? {
|
||||
if sstable_reader.key() == key_bytes {
|
||||
let value = sstable_reader.value().clone();
|
||||
return Ok(Some(value));
|
||||
@@ -217,10 +265,10 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
|
||||
/// Lookups the value corresponding to the key.
|
||||
pub async fn get_async<K: AsRef<[u8]>>(&self, key: K) -> io::Result<Option<TSSTable::Value>> {
|
||||
if let Some(block_addr) = self.sstable_index.search_block(key.as_ref()) {
|
||||
if let Some(block_addr) = self.sstable_index.get_block_with_key(key.as_ref()) {
|
||||
let mut sstable_reader = self.sstable_reader_block_async(block_addr).await?;
|
||||
let key_bytes = key.as_ref();
|
||||
while sstable_reader.advance().unwrap_or(false) {
|
||||
while sstable_reader.advance()? {
|
||||
if sstable_reader.key() == key_bytes {
|
||||
let value = sstable_reader.value().clone();
|
||||
return Ok(Some(value));
|
||||
@@ -259,3 +307,192 @@ impl<TSSTable: SSTable> Dictionary<TSSTable> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::ops::Range;
|
||||
use std::sync::{Arc, Mutex};
|
||||
|
||||
use common::OwnedBytes;
|
||||
|
||||
use super::Dictionary;
|
||||
use crate::MonotonicU64SSTable;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct PermissionedHandle {
|
||||
bytes: OwnedBytes,
|
||||
allowed_range: Mutex<Range<usize>>,
|
||||
}
|
||||
|
||||
impl PermissionedHandle {
|
||||
fn new(bytes: Vec<u8>) -> Self {
|
||||
let bytes = OwnedBytes::new(bytes);
|
||||
PermissionedHandle {
|
||||
allowed_range: Mutex::new(0..bytes.len()),
|
||||
bytes,
|
||||
}
|
||||
}
|
||||
|
||||
fn restrict(&self, range: Range<usize>) {
|
||||
*self.allowed_range.lock().unwrap() = range;
|
||||
}
|
||||
}
|
||||
|
||||
impl common::HasLen for PermissionedHandle {
|
||||
fn len(&self) -> usize {
|
||||
self.bytes.len()
|
||||
}
|
||||
}
|
||||
|
||||
impl common::file_slice::FileHandle for PermissionedHandle {
|
||||
fn read_bytes(&self, range: Range<usize>) -> std::io::Result<OwnedBytes> {
|
||||
let allowed_range = self.allowed_range.lock().unwrap();
|
||||
if !allowed_range.contains(&range.start) || !allowed_range.contains(&(range.end - 1)) {
|
||||
return Err(std::io::Error::new(
|
||||
std::io::ErrorKind::Other,
|
||||
format!("invalid range, allowed {allowed_range:?}, requested {range:?}"),
|
||||
));
|
||||
}
|
||||
|
||||
Ok(self.bytes.slice(range))
|
||||
}
|
||||
}
|
||||
|
||||
fn make_test_sstable() -> (Dictionary<MonotonicU64SSTable>, Arc<PermissionedHandle>) {
|
||||
let mut builder = Dictionary::<MonotonicU64SSTable>::builder(Vec::new()).unwrap();
|
||||
|
||||
// this makes 256k keys, enough to fill multiple blocks.
|
||||
for elem in 0..0x3ffff {
|
||||
let key = format!("{elem:05X}").into_bytes();
|
||||
builder.insert_cannot_fail(&key, &elem);
|
||||
}
|
||||
|
||||
let table = builder.finish().unwrap();
|
||||
let table = Arc::new(PermissionedHandle::new(table));
|
||||
let slice = common::file_slice::FileSlice::new(table.clone());
|
||||
|
||||
let dictionary = Dictionary::<MonotonicU64SSTable>::open(slice).unwrap();
|
||||
|
||||
// if the last block is id 0, tests are meaningless
|
||||
assert_ne!(dictionary.sstable_index.locate_with_ord(u64::MAX), 0);
|
||||
assert_eq!(dictionary.num_terms(), 0x3ffff);
|
||||
(dictionary, table)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ord_term_conversion() {
|
||||
let (dic, slice) = make_test_sstable();
|
||||
|
||||
let block = dic.sstable_index.get_block_with_ord(100_000);
|
||||
slice.restrict(block.byte_range);
|
||||
|
||||
let mut res = Vec::new();
|
||||
|
||||
// middle of a block
|
||||
assert!(dic.ord_to_term(100_000, &mut res).unwrap());
|
||||
assert_eq!(res, format!("{:05X}", 100_000).into_bytes());
|
||||
assert_eq!(dic.term_info_from_ord(100_000).unwrap().unwrap(), 100_000);
|
||||
assert_eq!(dic.get(&res).unwrap().unwrap(), 100_000);
|
||||
assert_eq!(dic.term_ord(&res).unwrap().unwrap(), 100_000);
|
||||
|
||||
// start of a block
|
||||
assert!(dic.ord_to_term(block.first_ordinal, &mut res).unwrap());
|
||||
assert_eq!(res, format!("{:05X}", block.first_ordinal).into_bytes());
|
||||
assert_eq!(
|
||||
dic.term_info_from_ord(block.first_ordinal)
|
||||
.unwrap()
|
||||
.unwrap(),
|
||||
block.first_ordinal
|
||||
);
|
||||
assert_eq!(dic.get(&res).unwrap().unwrap(), block.first_ordinal);
|
||||
assert_eq!(dic.term_ord(&res).unwrap().unwrap(), block.first_ordinal);
|
||||
|
||||
// end of a block
|
||||
let ordinal = block.first_ordinal - 1;
|
||||
let new_range = dic.sstable_index.get_block_with_ord(ordinal).byte_range;
|
||||
slice.restrict(new_range);
|
||||
assert!(dic.ord_to_term(ordinal, &mut res).unwrap());
|
||||
assert_eq!(res, format!("{:05X}", ordinal).into_bytes());
|
||||
assert_eq!(dic.term_info_from_ord(ordinal).unwrap().unwrap(), ordinal);
|
||||
assert_eq!(dic.get(&res).unwrap().unwrap(), ordinal);
|
||||
assert_eq!(dic.term_ord(&res).unwrap().unwrap(), ordinal);
|
||||
|
||||
// before first block
|
||||
// 1st block must be loaded for key-related operations
|
||||
let block = dic.sstable_index.get_block_with_ord(0);
|
||||
slice.restrict(block.byte_range);
|
||||
|
||||
assert!(dic.get(&b"$$$").unwrap().is_none());
|
||||
assert!(dic.term_ord(&b"$$$").unwrap().is_none());
|
||||
|
||||
// after last block
|
||||
// last block must be loaded for ord related operations
|
||||
let ordinal = 0x40000 + 10;
|
||||
let new_range = dic.sstable_index.get_block_with_ord(ordinal).byte_range;
|
||||
slice.restrict(new_range);
|
||||
assert!(!dic.ord_to_term(ordinal, &mut res).unwrap());
|
||||
assert!(dic.term_info_from_ord(ordinal).unwrap().is_none());
|
||||
|
||||
// last block isn't required to be loaded for key related operations
|
||||
slice.restrict(0..0);
|
||||
assert!(dic.get(&b"~~~").unwrap().is_none());
|
||||
assert!(dic.term_ord(&b"~~~").unwrap().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_range() {
|
||||
let (dic, slice) = make_test_sstable();
|
||||
|
||||
let start = dic
|
||||
.sstable_index
|
||||
.get_block_with_key(b"10000")
|
||||
.unwrap()
|
||||
.byte_range;
|
||||
let end = dic
|
||||
.sstable_index
|
||||
.get_block_with_key(b"18000")
|
||||
.unwrap()
|
||||
.byte_range;
|
||||
slice.restrict(start.start..end.end);
|
||||
|
||||
let mut stream = dic.range().ge(b"10000").lt(b"18000").into_stream().unwrap();
|
||||
|
||||
for i in 0x10000..0x18000 {
|
||||
assert!(stream.advance());
|
||||
assert_eq!(stream.term_ord(), i);
|
||||
assert_eq!(stream.value(), &i);
|
||||
assert_eq!(stream.key(), format!("{i:05X}").into_bytes());
|
||||
}
|
||||
assert!(!stream.advance());
|
||||
|
||||
// verify limiting the number of results reduce the size read
|
||||
slice.restrict(start.start..(end.end - 1));
|
||||
|
||||
let mut stream = dic
|
||||
.range()
|
||||
.ge(b"10000")
|
||||
.lt(b"18000")
|
||||
.limit(0xfff)
|
||||
.into_stream()
|
||||
.unwrap();
|
||||
|
||||
for i in 0x10000..0x10fff {
|
||||
assert!(stream.advance());
|
||||
assert_eq!(stream.term_ord(), i);
|
||||
assert_eq!(stream.value(), &i);
|
||||
assert_eq!(stream.key(), format!("{i:05X}").into_bytes());
|
||||
}
|
||||
// there might be more successful elements after, though how many is undefined
|
||||
|
||||
slice.restrict(0..slice.bytes.len());
|
||||
|
||||
let mut stream = dic.stream().unwrap();
|
||||
for i in 0..0x3ffff {
|
||||
assert!(stream.advance());
|
||||
assert_eq!(stream.term_ord(), i);
|
||||
assert_eq!(stream.value(), &i);
|
||||
assert_eq!(stream.key(), format!("{i:05X}").into_bytes());
|
||||
}
|
||||
assert!(!stream.advance());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,6 +117,7 @@ impl SSTable for MonotonicU64SSTable {
|
||||
/// `range_sstable[k1].end == range_sstable[k2].start`.
|
||||
///
|
||||
/// The first range is not required to start at `0`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct RangeSSTable;
|
||||
|
||||
impl SSTable for RangeSSTable {
|
||||
|
||||
@@ -3,23 +3,69 @@ use std::ops::Range;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{common_prefix_len, SSTableDataCorruption};
|
||||
use crate::{common_prefix_len, SSTableDataCorruption, TermOrdinal};
|
||||
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct SSTableIndex {
|
||||
blocks: Vec<BlockMeta>,
|
||||
}
|
||||
|
||||
impl SSTableIndex {
|
||||
/// Load an index from its binary representation
|
||||
pub fn load(data: &[u8]) -> Result<SSTableIndex, SSTableDataCorruption> {
|
||||
ciborium::de::from_reader(data).map_err(|_| SSTableDataCorruption)
|
||||
}
|
||||
|
||||
pub fn search_block(&self, key: &[u8]) -> Option<BlockAddr> {
|
||||
/// Get the [`BlockAddr`] of the requested block.
|
||||
pub(crate) fn get_block(&self, block_id: usize) -> Option<BlockAddr> {
|
||||
self.blocks
|
||||
.iter()
|
||||
.find(|block| &block.last_key_or_greater[..] >= key)
|
||||
.map(|block| block.block_addr.clone())
|
||||
.get(block_id)
|
||||
.map(|block_meta| block_meta.block_addr.clone())
|
||||
}
|
||||
|
||||
/// Get the block id of the block that woudl contain `key`.
|
||||
///
|
||||
/// Returns None if `key` is lexicographically after the last key recorded.
|
||||
pub(crate) fn locate_with_key(&self, key: &[u8]) -> Option<usize> {
|
||||
let pos = self
|
||||
.blocks
|
||||
.binary_search_by_key(&key, |block| &block.last_key_or_greater);
|
||||
match pos {
|
||||
Ok(pos) => Some(pos),
|
||||
Err(pos) => {
|
||||
if pos < self.blocks.len() {
|
||||
Some(pos)
|
||||
} else {
|
||||
// after end of last block: no block matches
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`BlockAddr`] of the block that would contain `key`.
|
||||
///
|
||||
/// Returns None if `key` is lexicographically after the last key recorded.
|
||||
pub fn get_block_with_key(&self, key: &[u8]) -> Option<BlockAddr> {
|
||||
self.locate_with_key(key).and_then(|id| self.get_block(id))
|
||||
}
|
||||
|
||||
pub(crate) fn locate_with_ord(&self, ord: TermOrdinal) -> usize {
|
||||
let pos = self
|
||||
.blocks
|
||||
.binary_search_by_key(&ord, |block| block.block_addr.first_ordinal);
|
||||
|
||||
match pos {
|
||||
Ok(pos) => pos,
|
||||
// Err(0) can't happen as the sstable starts with ordinal zero
|
||||
Err(pos) => pos - 1,
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the [`BlockAddr`] of the block containing the `ord`-th term.
|
||||
pub(crate) fn get_block_with_ord(&self, ord: TermOrdinal) -> BlockAddr {
|
||||
// locate_with_ord always returns an index within range
|
||||
self.get_block(self.locate_with_ord(ord)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +75,8 @@ pub struct BlockAddr {
|
||||
pub first_ordinal: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
struct BlockMeta {
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub(crate) struct BlockMeta {
|
||||
/// Any byte string that is lexicographically greater or equal to
|
||||
/// the last key in the block,
|
||||
/// and yet strictly smaller than the first key in the next block.
|
||||
@@ -98,26 +144,38 @@ mod tests {
|
||||
fn test_sstable_index() {
|
||||
let mut sstable_builder = SSTableIndexBuilder::default();
|
||||
sstable_builder.add_block(b"aaa", 10..20, 0u64);
|
||||
sstable_builder.add_block(b"bbbbbbb", 20..30, 564);
|
||||
sstable_builder.add_block(b"bbbbbbb", 20..30, 5u64);
|
||||
sstable_builder.add_block(b"ccc", 30..40, 10u64);
|
||||
sstable_builder.add_block(b"dddd", 40..50, 15u64);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
sstable_builder.serialize(&mut buffer).unwrap();
|
||||
let sstable_index = SSTableIndex::load(&buffer[..]).unwrap();
|
||||
assert_eq!(
|
||||
sstable_index.search_block(b"bbbde"),
|
||||
sstable_index.get_block_with_key(b"bbbde"),
|
||||
Some(BlockAddr {
|
||||
first_ordinal: 10u64,
|
||||
byte_range: 30..40
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(sstable_index.locate_with_key(b"aa").unwrap(), 0);
|
||||
assert_eq!(sstable_index.locate_with_key(b"aaa").unwrap(), 0);
|
||||
assert_eq!(sstable_index.locate_with_key(b"aab").unwrap(), 1);
|
||||
assert_eq!(sstable_index.locate_with_key(b"ccc").unwrap(), 2);
|
||||
assert!(sstable_index.locate_with_key(b"e").is_none());
|
||||
|
||||
assert_eq!(sstable_index.locate_with_ord(0), 0);
|
||||
assert_eq!(sstable_index.locate_with_ord(1), 0);
|
||||
assert_eq!(sstable_index.locate_with_ord(4), 0);
|
||||
assert_eq!(sstable_index.locate_with_ord(5), 1);
|
||||
assert_eq!(sstable_index.locate_with_ord(100), 3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_sstable_with_corrupted_data() {
|
||||
let mut sstable_builder = SSTableIndexBuilder::default();
|
||||
sstable_builder.add_block(b"aaa", 10..20, 0u64);
|
||||
sstable_builder.add_block(b"bbbbbbb", 20..30, 564);
|
||||
sstable_builder.add_block(b"bbbbbbb", 20..30, 5u64);
|
||||
sstable_builder.add_block(b"ccc", 30..40, 10u64);
|
||||
sstable_builder.add_block(b"dddd", 40..50, 15u64);
|
||||
let mut buffer: Vec<u8> = Vec::new();
|
||||
|
||||
@@ -19,6 +19,7 @@ where
|
||||
automaton: A,
|
||||
lower: Bound<Vec<u8>>,
|
||||
upper: Bound<Vec<u8>>,
|
||||
limit: Option<u64>,
|
||||
}
|
||||
|
||||
fn bound_as_byte_slice(bound: &Bound<Vec<u8>>) -> Bound<&[u8]> {
|
||||
@@ -41,6 +42,7 @@ where
|
||||
automaton,
|
||||
lower: Bound::Unbounded,
|
||||
upper: Bound::Unbounded,
|
||||
limit: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,24 +70,46 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Load no more data than what's required to to get `limit`
|
||||
/// matching entries.
|
||||
///
|
||||
/// The resulting [`Streamer`] can still return marginaly
|
||||
/// more than `limit` elements.
|
||||
pub fn limit(mut self, limit: u64) -> Self {
|
||||
self.limit = Some(limit);
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates the stream corresponding to the range
|
||||
/// of terms defined using the `StreamerBuilder`.
|
||||
pub fn into_stream(self) -> io::Result<Streamer<'a, TSSTable, A>> {
|
||||
// TODO Optimize by skipping to the right first block.
|
||||
let start_state = self.automaton.start();
|
||||
|
||||
let key_range = (
|
||||
bound_as_byte_slice(&self.lower),
|
||||
bound_as_byte_slice(&self.upper),
|
||||
);
|
||||
|
||||
let first_term = match &key_range.0 {
|
||||
Bound::Included(key) | Bound::Excluded(key) => self
|
||||
.term_dict
|
||||
.sstable_index
|
||||
.get_block_with_key(key)
|
||||
.map(|block| block.first_ordinal)
|
||||
.unwrap_or(0),
|
||||
Bound::Unbounded => 0,
|
||||
};
|
||||
|
||||
let delta_reader = self
|
||||
.term_dict
|
||||
.sstable_delta_reader_for_key_range(key_range)?;
|
||||
.sstable_delta_reader_for_key_range(key_range, self.limit)?;
|
||||
Ok(Streamer {
|
||||
automaton: self.automaton,
|
||||
states: vec![start_state],
|
||||
delta_reader,
|
||||
key: Vec::new(),
|
||||
term_ord: None,
|
||||
term_ord: first_term.checked_sub(1),
|
||||
lower_bound: self.lower,
|
||||
upper_bound: self.upper,
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ pub trait ValueReader: Default {
|
||||
|
||||
/// Loads a block.
|
||||
///
|
||||
/// Returns the number of bytes that were written.
|
||||
/// Returns the number of bytes that were read.
|
||||
fn load(&mut self, data: &[u8]) -> io::Result<usize>;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user