1use crate::{
5 column::{ColId, Salt},
6 compress::CompressionType,
7 error::{try_io, Error, Result},
8};
9use rand::Rng;
10use std::{collections::HashMap, path::Path};
11
12pub const CURRENT_VERSION: u32 = 8;
13const LAST_SUPPORTED_VERSION: u32 = 4;
16
17pub const DEFAULT_COMPRESSION_THRESHOLD: u32 = 4096;
18
19#[derive(Clone, Debug)]
21pub struct Options {
22 pub path: std::path::PathBuf,
24 pub columns: Vec<ColumnOptions>,
26 pub sync_wal: bool,
29 pub sync_data: bool,
32 pub stats: bool,
34 pub salt: Option<Salt>,
37 pub compression_threshold: HashMap<ColId, u32>,
41 #[cfg(any(test, feature = "instrumentation"))]
42 pub with_background_thread: bool,
44 #[cfg(any(test, feature = "instrumentation"))]
45 pub always_flush: bool,
47}
48
49#[derive(Clone, Debug, PartialEq, Eq)]
51pub struct ColumnOptions {
52 pub preimage: bool,
56 pub uniform: bool,
60 pub ref_counted: bool,
63 pub compression: CompressionType,
65 pub btree_index: bool,
68}
69
70#[derive(Clone, Debug)]
72pub struct Metadata {
73 pub salt: Salt,
75 pub version: u32,
77 pub columns: Vec<ColumnOptions>,
79}
80
81impl ColumnOptions {
82 fn as_string(&self) -> String {
83 format!(
84 "preimage: {}, uniform: {}, refc: {}, compression: {}, ordered: {}",
85 self.preimage, self.uniform, self.ref_counted, self.compression as u8, self.btree_index,
86 )
87 }
88
89 pub fn is_valid(&self) -> bool {
90 if self.ref_counted && !self.preimage {
91 log::error!(target: "parity-db", "Using `ref_counted` option without `preimage` enabled is not supported");
92 return false
93 }
94 true
95 }
96
97 fn from_string(s: &str) -> Option<Self> {
98 let mut split = s.split("sizes: ");
99 let vals = split.next()?;
100
101 let vals: HashMap<&str, &str> = vals
102 .split(", ")
103 .filter_map(|s| {
104 let mut pair = s.split(": ");
105 Some((pair.next()?, pair.next()?))
106 })
107 .collect();
108
109 let preimage = vals.get("preimage")?.parse().ok()?;
110 let uniform = vals.get("uniform")?.parse().ok()?;
111 let ref_counted = vals.get("refc")?.parse().ok()?;
112 let compression: u8 = vals.get("compression").and_then(|c| c.parse().ok()).unwrap_or(0);
113 let btree_index = vals.get("ordered").and_then(|c| c.parse().ok()).unwrap_or(false);
114
115 Some(ColumnOptions {
116 preimage,
117 uniform,
118 ref_counted,
119 compression: compression.into(),
120 btree_index,
121 })
122 }
123}
124
125impl Default for ColumnOptions {
126 fn default() -> ColumnOptions {
127 ColumnOptions {
128 preimage: false,
129 uniform: false,
130 ref_counted: false,
131 compression: CompressionType::NoCompression,
132 btree_index: false,
133 }
134 }
135}
136
137impl Options {
138 pub fn with_columns(path: &Path, num_columns: u8) -> Options {
139 Options {
140 path: path.into(),
141 sync_wal: true,
142 sync_data: true,
143 stats: true,
144 salt: None,
145 columns: (0..num_columns).map(|_| Default::default()).collect(),
146 compression_threshold: HashMap::new(),
147 #[cfg(any(test, feature = "instrumentation"))]
148 with_background_thread: true,
149 #[cfg(any(test, feature = "instrumentation"))]
150 always_flush: false,
151 }
152 }
153
154 pub fn write_metadata(&self, path: &Path, salt: &Salt) -> Result<()> {
156 self.write_metadata_with_version(path, salt, None)
157 }
158
159 pub fn write_metadata_file(&self, path: &Path, salt: &Salt) -> Result<()> {
161 self.write_metadata_file_with_version(path, salt, None)
162 }
163
164 pub fn write_metadata_with_version(
165 &self,
166 path: &Path,
167 salt: &Salt,
168 version: Option<u32>,
169 ) -> Result<()> {
170 let mut path = path.to_path_buf();
171 path.push("metadata");
172 self.write_metadata_file_with_version(&path, salt, version)
173 }
174
175 pub fn write_metadata_file_with_version(
176 &self,
177 path: &Path,
178 salt: &Salt,
179 version: Option<u32>,
180 ) -> Result<()> {
181 let mut metadata = vec![
182 format!("version={}", version.unwrap_or(CURRENT_VERSION)),
183 format!("salt={}", hex::encode(salt)),
184 ];
185 for i in 0..self.columns.len() {
186 metadata.push(format!("col{}={}", i, self.columns[i].as_string()));
187 }
188 try_io!(std::fs::write(path, metadata.join("\n")));
189 Ok(())
190 }
191
192 pub fn load_and_validate_metadata(&self, create: bool) -> Result<Metadata> {
193 let meta = Self::load_metadata(&self.path)?;
194
195 if let Some(meta) = meta {
196 if meta.columns.len() != self.columns.len() {
197 return Err(Error::InvalidConfiguration(format!(
198 "Column config mismatch. Expected {} columns, got {}",
199 self.columns.len(),
200 meta.columns.len()
201 )))
202 }
203
204 for c in 0..meta.columns.len() {
205 if meta.columns[c] != self.columns[c] {
206 return Err(Error::IncompatibleColumnConfig {
207 id: c as ColId,
208 reason: format!(
209 "Column config mismatch. Expected \"{}\", got \"{}\"",
210 self.columns[c].as_string(),
211 meta.columns[c].as_string(),
212 ),
213 })
214 }
215 }
216 Ok(meta)
217 } else if create {
218 let s: Salt = self.salt.unwrap_or_else(|| rand::thread_rng().gen());
219 self.write_metadata(&self.path, &s)?;
220 Ok(Metadata { version: CURRENT_VERSION, columns: self.columns.clone(), salt: s })
221 } else {
222 Err(Error::DatabaseNotFound)
223 }
224 }
225
226 pub fn load_metadata(path: &Path) -> Result<Option<Metadata>> {
227 let mut path = path.to_path_buf();
228 path.push("metadata");
229 Self::load_metadata_file(&path)
230 }
231
232 pub fn load_metadata_file(path: &Path) -> Result<Option<Metadata>> {
233 use std::{io::BufRead, str::FromStr};
234
235 if !path.exists() {
236 return Ok(None)
237 }
238 let file = std::io::BufReader::new(try_io!(std::fs::File::open(path)));
239 let mut salt = None;
240 let mut columns = Vec::new();
241 let mut version = 0;
242 for l in file.lines() {
243 let l = try_io!(l);
244 let mut vals = l.split('=');
245 let k = vals.next().ok_or_else(|| Error::Corruption("Bad metadata".into()))?;
246 let v = vals.next().ok_or_else(|| Error::Corruption("Bad metadata".into()))?;
247 if k == "version" {
248 version =
249 u32::from_str(v).map_err(|_| Error::Corruption("Bad version string".into()))?;
250 } else if k == "salt" {
251 let salt_slice =
252 hex::decode(v).map_err(|_| Error::Corruption("Bad salt string".into()))?;
253 let mut s = Salt::default();
254 s.copy_from_slice(&salt_slice);
255 salt = Some(s);
256 } else if k.starts_with("col") {
257 let col = ColumnOptions::from_string(v)
258 .ok_or_else(|| Error::Corruption("Bad column metadata".into()))?;
259 columns.push(col);
260 }
261 }
262 if version < LAST_SUPPORTED_VERSION {
263 return Err(Error::InvalidConfiguration(format!(
264 "Unsupported database version {version}. Expected {CURRENT_VERSION}"
265 )))
266 }
267 let salt = salt.ok_or_else(|| Error::InvalidConfiguration("Missing salt value".into()))?;
268 Ok(Some(Metadata { version, columns, salt }))
269 }
270
271 pub fn is_valid(&self) -> bool {
272 for option in self.columns.iter() {
273 if !option.is_valid() {
274 return false
275 }
276 }
277 true
278 }
279}
280
281impl Metadata {
282 pub fn columns_to_migrate(&self) -> std::collections::BTreeSet<u8> {
283 std::collections::BTreeSet::new()
284 }
285}