1616 *
1717 */
1818
19+ use snap:: raw:: Encoder ;
20+ use std:: borrow:: Cow ;
1921use std:: fmt:: Debug ;
2022use std:: marker:: PhantomData ;
2123
22- use snap:: raw:: Encoder ;
23-
2424use crate :: error;
2525use crate :: error:: ErrorKind ;
2626use crate :: memdx:: datatype:: DataTypeFlag ;
2727use crate :: options:: agent:: { CompressionConfig , CompressionMode } ;
2828
2929pub ( crate ) trait Compressor : Send + Sync + Debug {
3030 fn new ( compression_config : & CompressionConfig ) -> Self ;
31- // This is a bit of a weird signature, but it allows us to avoid allocations when no compression occurs.
3231 fn compress < ' a > (
33- & ' a mut self ,
32+ & mut self ,
3433 connection_supports_snappy : bool ,
3534 datatype : DataTypeFlag ,
3635 input : & ' a [ u8 ] ,
37- ) -> error:: Result < ( & ' a [ u8 ] , u8 ) > ;
36+ ) -> error:: Result < ( Cow < ' a , [ u8 ] > , u8 ) > ;
3837}
3938
4039#[ derive( Debug ) ]
@@ -64,12 +63,8 @@ pub(crate) struct StdCompressor {
6463 compression_enabled : bool ,
6564 compression_min_size : usize ,
6665 compression_min_ratio : f64 ,
67-
68- compressed_value : Vec < u8 > ,
6966}
7067
71- impl StdCompressor { }
72-
7368impl Compressor for StdCompressor {
7469 fn new ( compression_config : & CompressionConfig ) -> Self {
7570 let ( compression_enabled, compression_min_size, compression_min_ratio) =
@@ -85,33 +80,31 @@ impl Compressor for StdCompressor {
8580 compression_enabled,
8681 compression_min_size,
8782 compression_min_ratio,
88-
89- compressed_value : Vec :: new ( ) ,
9083 }
9184 }
9285
9386 fn compress < ' a > (
94- & ' a mut self ,
87+ & mut self ,
9588 connection_supports_snappy : bool ,
9689 datatype : DataTypeFlag ,
9790 input : & ' a [ u8 ] ,
98- ) -> error:: Result < ( & ' a [ u8 ] , u8 ) > {
91+ ) -> error:: Result < ( Cow < ' a , [ u8 ] > , u8 ) > {
9992 if !connection_supports_snappy || !self . compression_enabled {
100- return Ok ( ( input, u8:: from ( datatype) ) ) ;
93+ return Ok ( ( Cow :: Borrowed ( input) , u8:: from ( datatype) ) ) ;
10194 }
10295
10396 let datatype = u8:: from ( datatype) ;
10497
10598 // If the packet is already compressed then we don't want to compress it again.
10699 if datatype & u8:: from ( DataTypeFlag :: Compressed ) != 0 {
107- return Ok ( ( input, datatype) ) ;
100+ return Ok ( ( Cow :: Borrowed ( input) , datatype) ) ;
108101 }
109102
110103 let packet_size = input. len ( ) ;
111104
112105 // Only compress values that are large enough to be worthwhile.
113106 if packet_size <= self . compression_min_size {
114- return Ok ( ( input, datatype) ) ;
107+ return Ok ( ( Cow :: Borrowed ( input) , datatype) ) ;
115108 }
116109
117110 let mut encoder = Encoder :: new ( ) ;
@@ -121,14 +114,135 @@ impl Compressor for StdCompressor {
121114
122115 // Only return the compressed value if the ratio of compressed:original is small enough.
123116 if compressed_value. len ( ) as f64 / packet_size as f64 > self . compression_min_ratio {
124- return Ok ( ( input, datatype) ) ;
117+ return Ok ( ( Cow :: Borrowed ( input) , datatype) ) ;
125118 }
126119
127- self . compressed_value = compressed_value;
128-
129120 Ok ( (
130- & self . compressed_value ,
121+ Cow :: Owned ( compressed_value) ,
131122 datatype | u8:: from ( DataTypeFlag :: Compressed ) ,
132123 ) )
133124 }
134125}
126+
127+ #[ cfg( test) ]
128+ mod tests {
129+ use super :: * ;
130+ use std:: borrow:: Cow ;
131+
132+ fn enabled_config ( min_size : usize , min_ratio : f64 ) -> CompressionConfig {
133+ CompressionConfig :: new ( CompressionMode :: Enabled {
134+ min_size,
135+ min_ratio,
136+ } )
137+ }
138+
139+ fn disabled_config ( ) -> CompressionConfig {
140+ CompressionConfig :: new ( CompressionMode :: Disabled )
141+ }
142+
143+ #[ test]
144+ fn disabled_compression_returns_input_unchanged ( ) {
145+ let mut compressor = StdCompressor :: new ( & disabled_config ( ) ) ;
146+ let input = b"hello world" ;
147+
148+ let ( output, dt) = compressor
149+ . compress ( true , DataTypeFlag :: Json , input)
150+ . unwrap ( ) ;
151+
152+ assert ! ( matches!( output, Cow :: Borrowed ( _) ) ) ;
153+ assert_eq ! ( & * output, input. as_slice( ) ) ;
154+ assert_eq ! ( dt, u8 :: from( DataTypeFlag :: Json ) ) ;
155+ }
156+
157+ #[ test]
158+ fn connection_without_snappy_returns_input_unchanged ( ) {
159+ let mut compressor = StdCompressor :: new ( & enabled_config ( 0 , 1.0 ) ) ;
160+ let input = b"hello world" ;
161+
162+ let ( output, dt) = compressor
163+ . compress ( false , DataTypeFlag :: Json , input)
164+ . unwrap ( ) ;
165+
166+ assert ! ( matches!( output, Cow :: Borrowed ( _) ) ) ;
167+ assert_eq ! ( & * output, input. as_slice( ) ) ;
168+ }
169+
170+ #[ test]
171+ fn already_compressed_returns_input_unchanged ( ) {
172+ let mut compressor = StdCompressor :: new ( & enabled_config ( 0 , 1.0 ) ) ;
173+ let input = b"already compressed data" ;
174+
175+ let ( output, dt) = compressor
176+ . compress ( true , DataTypeFlag :: Compressed , input)
177+ . unwrap ( ) ;
178+
179+ assert ! ( matches!( output, Cow :: Borrowed ( _) ) ) ;
180+ assert_eq ! ( & * output, input. as_slice( ) ) ;
181+ assert_eq ! ( dt, u8 :: from( DataTypeFlag :: Compressed ) ) ;
182+ }
183+
184+ #[ test]
185+ fn input_below_min_size_returns_input_unchanged ( ) {
186+ let mut compressor = StdCompressor :: new ( & enabled_config ( 1024 , 1.0 ) ) ;
187+ let input = b"small" ;
188+
189+ let ( output, dt) = compressor
190+ . compress ( true , DataTypeFlag :: Json , input)
191+ . unwrap ( ) ;
192+
193+ assert ! ( matches!( output, Cow :: Borrowed ( _) ) ) ;
194+ assert_eq ! ( & * output, input. as_slice( ) ) ;
195+ assert_eq ! ( dt, u8 :: from( DataTypeFlag :: Json ) ) ;
196+ }
197+
198+ #[ test]
199+ fn compressible_input_returns_owned_with_compressed_flag ( ) {
200+ let mut compressor = StdCompressor :: new ( & enabled_config ( 0 , 1.0 ) ) ;
201+ // Highly compressible: repeated bytes.
202+ let input = vec ! [ b'a' ; 4096 ] ;
203+
204+ let ( output, dt) = compressor
205+ . compress ( true , DataTypeFlag :: Json , & input)
206+ . unwrap ( ) ;
207+
208+ assert ! ( matches!( output, Cow :: Owned ( _) ) ) ;
209+ assert ! ( output. len( ) < input. len( ) ) ;
210+ assert_eq ! (
211+ dt,
212+ u8 :: from( DataTypeFlag :: Json ) | u8 :: from( DataTypeFlag :: Compressed )
213+ ) ;
214+
215+ // Verify it round-trips through snappy.
216+ let decompressed = snap:: raw:: Decoder :: new ( ) . decompress_vec ( & output) . unwrap ( ) ;
217+ assert_eq ! ( decompressed, input) ;
218+ }
219+
220+ #[ test]
221+ fn poor_ratio_returns_input_unchanged ( ) {
222+ // Set a very aggressive ratio that compressed output can't beat.
223+ let mut compressor = StdCompressor :: new ( & enabled_config ( 0 , 0.01 ) ) ;
224+ let input = vec ! [ b'a' ; 256 ] ;
225+
226+ let ( output, dt) = compressor
227+ . compress ( true , DataTypeFlag :: Json , & input)
228+ . unwrap ( ) ;
229+
230+ assert ! ( matches!( output, Cow :: Borrowed ( _) ) ) ;
231+ assert_eq ! ( & * output, input. as_slice( ) ) ;
232+ assert_eq ! ( dt, u8 :: from( DataTypeFlag :: Json ) ) ;
233+ }
234+
235+ #[ test]
236+ fn compression_manager_creates_compressor ( ) {
237+ let manager = CompressionManager :: < StdCompressor > :: new ( enabled_config ( 0 , 1.0 ) ) ;
238+ let mut compressor = manager. compressor ( ) ;
239+ let input = vec ! [ b'x' ; 4096 ] ;
240+
241+ let ( output, _dt) = compressor
242+ . compress ( true , DataTypeFlag :: None , & input)
243+ . unwrap ( ) ;
244+
245+ assert ! ( matches!( output, Cow :: Owned ( _) ) ) ;
246+ assert ! ( output. len( ) < input. len( ) ) ;
247+ }
248+ }
0 commit comments