1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
// Different from compressor and decompressor configs, flags change the format
// of the compressed data.
// New flags may be added in over time in a backward-compatible way.
use std::cmp::min;
use std::convert::{TryFrom, TryInto};
use crate::bit_reader::BitReader;
use crate::bit_writer::BitWriter;
use crate::bits;
use crate::constants::{
BITS_TO_ENCODE_DELTA_ENCODING_ORDER, BITS_TO_ENCODE_N_ENTRIES, MAX_DELTA_ENCODING_ORDER,
};
use crate::errors::{QCompressError, QCompressResult};
use crate::CompressorConfig;
/// The configuration stored in a Quantile-compressed header.
///
/// During compression, flags are determined based on your `CompressorConfig`
/// and the `q_compress` version.
/// Flags affect the encoding of the rest of the file, so decompressing with
/// the wrong flags will likely cause a corruption error.
///
/// You will not need to manually instantiate flags; that should be done
/// internally by `Compressor::from_config`.
/// However, in some circumstances you may want to inspect flags during
/// decompression.
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct Flags {
/// Whether to use 5 bits to encode the length of a prefix Huffman code,
/// as opposed to 4.
/// The first versions of `q_compress` used 4, which was insufficient for
/// Huffman codes that could reach up to 23 in length
/// (23 >= 16 = 2^4)
/// in spiky distributions with high compression level.
/// In later versions, this flag is always true.
///
/// Introduced in 0.5.0.
pub use_5_bit_code_len: bool,
/// How many times delta encoding was applied during compression.
/// This is stored as 3 bits to express 0-7.
/// See `CompressorConfig` for more details.
///
/// Introduced in 0.6.0.
pub delta_encoding_order: usize,
/// Whether to use the minimum number of bits to encode the count of each
/// prefix, rather than using a constant number of bits.
/// This can reduce file size slightly for small data.
/// In later versions, this flag is always true.
///
/// Introduced in 0.9.1.
pub use_min_count_encoding: bool,
/// Whether to enable greatest common divisor multipliers for each
/// prefix.
/// This adds an optional multiplier to each prefix metadata, so that each
/// unsigned number is decoded as `x = prefix_lower + offset * gcd`.
/// This can improve compression ratio in some cases, e.g. when the
/// numbers are all integer multiples of 100 or all integer-valued floats.
///
/// Introduced in 0.10.0.
pub use_gcds: bool,
/// Whether to release control to a wrapping columnar format.
/// This causes q_compress to omit count and compressed size metadata
/// and also break each chuk into finer data pages.
///
/// Introduced in 0.11.2.
pub use_wrapped_mode: bool,
}
impl TryFrom<Vec<bool>> for Flags {
type Error = QCompressError;
fn try_from(bools: Vec<bool>) -> QCompressResult<Self> {
let mut flags = Flags {
use_5_bit_code_len: false,
delta_encoding_order: 0,
use_min_count_encoding: false,
use_gcds: false,
use_wrapped_mode: false,
};
let mut bit_iter = bools.iter();
flags.use_5_bit_code_len = bit_iter.next() == Some(&true);
let mut delta_encoding_bits = Vec::new();
while delta_encoding_bits.len() < BITS_TO_ENCODE_DELTA_ENCODING_ORDER {
delta_encoding_bits.push(bit_iter.next().cloned().unwrap_or(false));
}
flags.delta_encoding_order = bits::bits_to_usize(&delta_encoding_bits);
flags.use_min_count_encoding = bit_iter.next() == Some(&true);
flags.use_gcds = bit_iter.next() == Some(&true);
flags.use_wrapped_mode = bit_iter.next() == Some(&true);
// if we ever add another bit flag, it will increase file size by 1 byte when on
for &bit in bit_iter {
if bit {
return Err(QCompressError::compatibility(
"cannot parse flags; likely written by newer version of q_compress",
));
}
}
Ok(flags)
}
}
impl TryInto<Vec<bool>> for &Flags {
type Error = QCompressError;
fn try_into(self) -> QCompressResult<Vec<bool>> {
let mut res = vec![self.use_5_bit_code_len];
if self.delta_encoding_order > MAX_DELTA_ENCODING_ORDER {
return Err(QCompressError::invalid_argument(format!(
"delta encoding order may not exceed {} (was {})",
MAX_DELTA_ENCODING_ORDER, self.delta_encoding_order,
)));
}
let delta_bits = bits::usize_truncated_to_bits(
self.delta_encoding_order,
BITS_TO_ENCODE_DELTA_ENCODING_ORDER,
);
res.extend(delta_bits);
res.push(self.use_min_count_encoding);
res.push(self.use_gcds);
res.push(self.use_wrapped_mode);
let necessary_len = res
.iter()
.rposition(|&bit| bit)
.map(|idx| idx + 1)
.unwrap_or(0);
res.truncate(necessary_len);
Ok(res)
}
}
impl Flags {
pub(crate) fn parse_from(reader: &mut BitReader) -> QCompressResult<Self> {
reader.aligned_byte_idx()?; // assert it's byte-aligned
let mut bools = Vec::new();
loop {
bools.extend(reader.read(7)?);
if !reader.read_one()? {
break;
}
}
Self::try_from(bools)
}
pub(crate) fn write(&self, writer: &mut BitWriter) -> QCompressResult<()> {
let bools: Vec<bool> = self.try_into()?;
// reserve 1 bit at the end of every byte for whether there is a following
// byte
for i in 0_usize..(bools.len() / 7) + 1 {
let start = i * 7;
let end = min(start + 7, bools.len());
writer.write(&bools[start..end]);
if end < bools.len() {
writer.write_one(true);
}
}
writer.finish_byte();
Ok(())
}
pub(crate) fn check_mode(&self, expect_wrapped_mode: bool) -> QCompressResult<()> {
if self.use_wrapped_mode != expect_wrapped_mode {
Err(QCompressError::invalid_argument(
"found conflicting standalone/wrapped modes between decompressor and header",
))
} else {
Ok(())
}
}
pub(crate) fn bits_to_encode_code_len(&self) -> usize {
if self.use_5_bit_code_len {
5
} else {
4
}
}
pub(crate) fn bits_to_encode_count(&self, n: usize) -> usize {
// If we use wrapped mode, we don't encode the prefix counts at all (even
// though they are nonzero). This propagates nicely through prefix
// optimization.
if self.use_wrapped_mode {
0
} else if self.use_min_count_encoding {
bits::bits_to_encode(n)
} else {
BITS_TO_ENCODE_N_ENTRIES
}
}
pub(crate) fn from_config(config: &CompressorConfig, use_wrapped_mode: bool) -> Self {
Flags {
use_5_bit_code_len: true,
delta_encoding_order: config.delta_encoding_order,
use_min_count_encoding: true,
use_gcds: config.use_gcds,
use_wrapped_mode,
}
}
}