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 218 219 220
/*! This crate implements various [CqlType](https://docs.rs/cql_model/0.2/cql_model/trait.CqlType.html) derivatives for storing `Option<f64>` values in a CQL database. Will allocate 9 bytes per value [linked](https://docs.rs/cql_db/0.2/cql_db/fn.link_dimensions.html). # Benchmarks Benchmarks supplied below are fairly rudimentary (and rounded) and are there to give a rough idea of relative costs. Full benchmark code can be found in [github](https://github.com/AndrewSisley/CQLDb/tree/master/cql_storage_types/cql_nullable_f64) and can be run with `rustup run nightly cargo bench`. Operation | Database dimensions | Mean time (ns) --- | --- | --- Single point read | 1 | 3 100 (+/- 200) Single point read | 4 | 16 400 (+/- 900) Single point write | 1 | 2 800 (+/- 300) Single point write | 4 | 15 400 (+/- 1 000) Stream read 1 point | 1 | 2 500 (+/- 300) Stream read 1 point | 4 | 15 300 (+/- 800) Stream read 50 000 points | 1 | 27 300 000 (+/- 500 000) Stream read 50 000 points | 4 | 27 500 000 (+/- 150 000) # Examples The following creates a 1D database, writes 2 values to it, and then streams them into an array. ``` # use std::io::{ Cursor, SeekFrom, Seek }; # use cql_nullable_f64::{ NullableF64, unpack_stream }; # # const DATABASE_LOCATION: &str = "./.test_db"; const N_VALUES_TO_READ: usize = 3; # use std::error::Error; # use std::fs::remove_file; # fn main() -> Result<(), Box<dyn Error>> { # let _ = remove_file(format!("{}{}", DATABASE_LOCATION, "/db")); # let _ = remove_file(format!("{}{}", DATABASE_LOCATION, "/ax")); let base_point = [1]; let value1 = Some(-1.6); let value3 = Some(5.4); cql_db::create_db::<NullableF64>( DATABASE_LOCATION, &[3] )?; cql_db::write_value::<NullableF64>( DATABASE_LOCATION, &base_point, value1 )?; cql_db::write_value::<NullableF64>( DATABASE_LOCATION, &[base_point[0] + 2], value3 )?; let mut result: [Option<f64>; N_VALUES_TO_READ] = [None; N_VALUES_TO_READ]; let mut stream = Cursor::new(Vec::new()); cql_db::read_to_stream::<NullableF64>( DATABASE_LOCATION, &mut stream, &base_point, N_VALUES_TO_READ as u64 )?; stream.seek(SeekFrom::Start(0)).unwrap(); unpack_stream(&mut stream, N_VALUES_TO_READ, |idx, value| { result[idx] = value })?; assert_eq!(result[0], value1); assert_eq!(result[1], None); assert_eq!(result[2], value3); # Ok(()) # } ``` */ #![doc(html_root_url = "https://docs.rs/cql_nullable_f64/0.2.2")] use std::fs::{ File, OpenOptions }; use std::io; use std::io::{ Read, Write, Cursor, SeekFrom, Seek }; use byteorder::{ ReadBytesExt, WriteBytesExt, LittleEndian }; use cql_model::{ CqlType, CqlWritable, CqlReadable, CqlStreamReadable }; const HAS_VALUE_FLAG: u8 = 1; const NULL_FLAG: u8 = 0; const CONTENT_SIZE: usize = 8; const HAS_VALUE_SIZE: usize = 1; /// Static struct for declaring that you want to work with `Option<f64>` values in a CQL database. /// /// Stateless - used for type information only. pub struct NullableF64; impl CqlType for NullableF64 { type ValueType = Option<f64>; const VALUE_SIZE: usize = HAS_VALUE_SIZE + CONTENT_SIZE; } impl CqlWritable for NullableF64 { fn write_to_db(db_location: &str, value_location: u64, input_value: Self::ValueType) -> io::Result<()> { let mut file = OpenOptions::new().write(true).open(db_location)?; // unwrap should be considered safe by this point, with earlier checks in the cql_db crate (if not deliberately unchecked) file.seek(SeekFrom::Start(value_location * Self::VALUE_SIZE as u64)).unwrap(); match input_value { None => { file.write_all(&[NULL_FLAG; HAS_VALUE_SIZE]) } Some(value) => { let mut buffer = vec![HAS_VALUE_FLAG]; buffer.write_f64::<LittleEndian>(value)?; file.write_all(&buffer) } } } } impl CqlReadable for NullableF64 { fn read_from_db(db_location: &str, value_location: u64) -> io::Result<Self::ValueType> { let mut file = File::open(&db_location)?; // unwrap should be considered safe by this point, with earlier checks in the cql_db crate (if not deliberately unchecked) file.seek(SeekFrom::Start(value_location * Self::VALUE_SIZE as u64)).unwrap(); let mut null_buffer = [0; HAS_VALUE_SIZE]; match file.read_exact(&mut null_buffer) { Err(e) => { // ignore io::ErrorKind::UnexpectedEof and continue if e.kind() != io::ErrorKind::UnexpectedEof { return Err(e) } } _ => { } } if null_buffer[0] == NULL_FLAG { return Ok(None) } let mut value_buffer = [0; CONTENT_SIZE]; file.read_exact(&mut value_buffer)?; let mut rdr = Cursor::new(value_buffer); Ok(Some(rdr.read_f64::<LittleEndian>()?)) } } impl CqlStreamReadable for NullableF64 { fn read_to_stream(db_location: &str, stream: &mut dyn Write, value_location: u64, n_values: u64) -> io::Result<()> { let mut file = File::open(&db_location)?; // unwrap should be considered safe by this point, with earlier checks in the cql_db crate (if not deliberately unchecked) file.seek(SeekFrom::Start(value_location * Self::VALUE_SIZE as u64)).unwrap(); for _i in 0..n_values { let mut buffer = [0; Self::VALUE_SIZE]; match file.read_exact(&mut buffer) { Err(e) => { // ignore io::ErrorKind::UnexpectedEof and continue if e.kind() != io::ErrorKind::UnexpectedEof { return Err(e) } } _ => { } } stream.write_all(&mut buffer)?; } stream.flush() } } /// Unpacks `n_values` of Option<f64> from a stream, calling `value_handler` with each value and it's index. /// /// # Errors /// /// Will return any [I/O errors](https://doc.rust-lang.org/nightly/std/io/enum.ErrorKind.html) encountered during the execution of the function. If an error /// is returned, it may be that values have already been fed into the `value_handler`. /// /// # Panics /// /// Function does not actively defend against panics, and may do so if given invalid parameters. If the function panics it may be that values have /// already been fed into the `value_handler`. /// /// # Examples /// /// ```ignore /// cql_db::read_to_stream::<NullableF64>( /// DATABASE_LOCATION, /// &mut stream, /// &base_point, /// N_VALUES_TO_READ as u64 /// )?; /// /// stream.seek(SeekFrom::Start(0)); /// /// unpack_stream(&mut stream, N_VALUES_TO_READ, |idx, value| { /// result[idx] = value /// })?; /// ``` pub fn unpack_stream<F>(stream: &mut Cursor<Vec<u8>>, n_values: usize, mut value_handler: F) -> io::Result<()> where F: FnMut(usize, Option<f64>) { for index in 0..n_values { let mut buffer = [0; NullableF64::VALUE_SIZE]; stream.read_exact(&mut buffer)?; if buffer[0] == NULL_FLAG { value_handler(index, None); } else { let mut rdr = Cursor::new(&buffer[1..NullableF64::VALUE_SIZE]); value_handler(index, Some(rdr.read_f64::<LittleEndian>()?)); } } Ok(()) }