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 = 9;
13const LAST_SUPPORTED_VERSION: u32 = 4;
17
18pub const DEFAULT_COMPRESSION_THRESHOLD: u32 = 4096;
19
20#[derive(Clone, Debug)]
22pub struct Options {
23 pub path: std::path::PathBuf,
25 pub columns: Vec<ColumnOptions>,
27 pub sync_wal: bool,
30 pub sync_data: bool,
33 pub stats: bool,
35 pub salt: Option<Salt>,
38 pub compression_threshold: HashMap<ColId, u32>,
42 #[cfg(any(test, feature = "instrumentation"))]
43 pub with_background_thread: bool,
45 #[cfg(any(test, feature = "instrumentation"))]
46 pub always_flush: bool,
48}
49
50#[derive(Clone, Debug, PartialEq, Eq)]
52pub struct ColumnOptions {
53 pub preimage: bool,
57 pub uniform: bool,
61 pub ref_counted: bool,
64 pub compression: CompressionType,
66 pub btree_index: bool,
69 pub multitree: bool,
72 pub append_only: bool,
74 pub allow_direct_node_access: bool,
77}
78
79#[derive(Clone, Debug)]
81pub struct Metadata {
82 pub salt: Salt,
84 pub version: u32,
86 pub columns: Vec<ColumnOptions>,
88}
89
90impl ColumnOptions {
91 fn as_string(&self) -> String {
92 format!(
93 "preimage: {}, uniform: {}, refc: {}, compression: {}, ordered: {}, multitree: {}, append_only: {}, allow_direct_node_access: {}",
94 self.preimage,
95 self.uniform,
96 self.ref_counted,
97 self.compression as u8,
98 self.btree_index,
99 self.multitree,
100 self.append_only,
101 self.allow_direct_node_access,
102 )
103 }
104
105 pub fn is_valid(&self) -> bool {
106 if self.ref_counted && !self.preimage {
107 log::error!(target: "parity-db", "Using `ref_counted` option without `preimage` enabled is not supported");
108 return false
109 }
110 if self.ref_counted && self.append_only {
111 log::error!(target: "parity-db", "`ref_counted` option is redundant when `append_only` is enabled");
112 return false
113 }
114 if self.multitree && self.compression != CompressionType::NoCompression {
115 log::error!(target: "parity-db", "Compression is not currently supported with multitree columns");
116 return false
117 }
118 true
119 }
120
121 fn from_string(s: &str) -> Option<Self> {
122 let mut split = s.split("sizes: ");
123 let vals = split.next()?;
124
125 let vals: HashMap<&str, &str> = vals
126 .split(", ")
127 .filter_map(|s| {
128 let mut pair = s.split(": ");
129 Some((pair.next()?, pair.next()?))
130 })
131 .collect();
132
133 let preimage = vals.get("preimage")?.parse().ok()?;
134 let uniform = vals.get("uniform")?.parse().ok()?;
135 let ref_counted = vals.get("refc")?.parse().ok()?;
136 let compression: u8 = vals.get("compression").and_then(|c| c.parse().ok()).unwrap_or(0);
137 let btree_index = vals.get("ordered").and_then(|c| c.parse().ok()).unwrap_or(false);
138 let multitree = vals.get("multitree").and_then(|c| c.parse().ok()).unwrap_or(false);
139 let append_only = vals.get("append_only").and_then(|c| c.parse().ok()).unwrap_or(false);
140 let allow_direct_node_access = vals
141 .get("allow_direct_node_access")
142 .and_then(|c| c.parse().ok())
143 .unwrap_or(false);
144
145 Some(ColumnOptions {
146 preimage,
147 uniform,
148 ref_counted,
149 compression: compression.into(),
150 btree_index,
151 multitree,
152 append_only,
153 allow_direct_node_access,
154 })
155 }
156}
157
158impl Default for ColumnOptions {
159 fn default() -> ColumnOptions {
160 ColumnOptions {
161 preimage: false,
162 uniform: false,
163 ref_counted: false,
164 compression: CompressionType::NoCompression,
165 btree_index: false,
166 multitree: false,
167 append_only: false,
168 allow_direct_node_access: false,
169 }
170 }
171}
172
173impl Options {
174 pub fn with_columns(path: &Path, num_columns: u8) -> Options {
175 Options {
176 path: path.into(),
177 sync_wal: true,
178 sync_data: true,
179 stats: true,
180 salt: None,
181 columns: (0..num_columns).map(|_| Default::default()).collect(),
182 compression_threshold: HashMap::new(),
183 #[cfg(any(test, feature = "instrumentation"))]
184 with_background_thread: true,
185 #[cfg(any(test, feature = "instrumentation"))]
186 always_flush: false,
187 }
188 }
189
190 pub fn write_metadata(&self, path: &Path, salt: &Salt) -> Result<()> {
192 self.write_metadata_with_version(path, salt, None)
193 }
194
195 pub fn write_metadata_file(&self, path: &Path, salt: &Salt) -> Result<()> {
197 self.write_metadata_file_with_version(path, salt, None)
198 }
199
200 pub fn write_metadata_with_version(
201 &self,
202 path: &Path,
203 salt: &Salt,
204 version: Option<u32>,
205 ) -> Result<()> {
206 let mut path = path.to_path_buf();
207 path.push("metadata");
208 self.write_metadata_file_with_version(&path, salt, version)
209 }
210
211 pub fn write_metadata_file_with_version(
212 &self,
213 path: &Path,
214 salt: &Salt,
215 version: Option<u32>,
216 ) -> Result<()> {
217 let mut metadata = vec![
218 format!("version={}", version.unwrap_or(CURRENT_VERSION)),
219 format!("salt={}", hex::encode(salt)),
220 ];
221 for i in 0..self.columns.len() {
222 metadata.push(format!("col{}={}", i, self.columns[i].as_string()));
223 }
224 try_io!(std::fs::write(path, metadata.join("\n")));
225 Ok(())
226 }
227
228 pub fn load_and_validate_metadata(&self, create: bool) -> Result<Metadata> {
229 let meta = Self::load_metadata(&self.path)?;
230
231 if let Some(meta) = meta {
232 if meta.columns.len() != self.columns.len() {
233 return Err(Error::InvalidConfiguration(format!(
234 "Column config mismatch. Expected {} columns, got {}",
235 self.columns.len(),
236 meta.columns.len()
237 )))
238 }
239
240 for c in 0..meta.columns.len() {
241 if meta.columns[c] != self.columns[c] {
242 return Err(Error::IncompatibleColumnConfig {
243 id: c as ColId,
244 reason: format!(
245 "Column config mismatch. Expected \"{}\", got \"{}\"",
246 self.columns[c].as_string(),
247 meta.columns[c].as_string(),
248 ),
249 })
250 }
251 }
252 Ok(meta)
253 } else if create {
254 let s: Salt = self.salt.unwrap_or_else(|| rand::rng().random());
255 self.write_metadata(&self.path, &s)?;
256 Ok(Metadata { version: CURRENT_VERSION, columns: self.columns.clone(), salt: s })
257 } else {
258 Err(Error::DatabaseNotFound)
259 }
260 }
261
262 pub fn load_metadata(path: &Path) -> Result<Option<Metadata>> {
263 let mut path = path.to_path_buf();
264 path.push("metadata");
265 Self::load_metadata_file(&path)
266 }
267
268 pub fn load_metadata_file(path: &Path) -> Result<Option<Metadata>> {
269 use std::{io::BufRead, str::FromStr};
270
271 if !path.exists() {
272 return Ok(None)
273 }
274 let file = std::io::BufReader::new(try_io!(std::fs::File::open(path)));
275 let mut salt = None;
276 let mut columns = Vec::new();
277 let mut version = 0;
278 for l in file.lines() {
279 let l = try_io!(l);
280 let mut vals = l.split('=');
281 let k = vals.next().ok_or_else(|| Error::Corruption("Bad metadata".into()))?;
282 let v = vals.next().ok_or_else(|| Error::Corruption("Bad metadata".into()))?;
283 if k == "version" {
284 version =
285 u32::from_str(v).map_err(|_| Error::Corruption("Bad version string".into()))?;
286 } else if k == "salt" {
287 let salt_slice =
288 hex::decode(v).map_err(|_| Error::Corruption("Bad salt string".into()))?;
289 let mut s = Salt::default();
290 s.copy_from_slice(&salt_slice);
291 salt = Some(s);
292 } else if k.starts_with("col") {
293 let col = ColumnOptions::from_string(v)
294 .ok_or_else(|| Error::Corruption("Bad column metadata".into()))?;
295 columns.push(col);
296 }
297 }
298 if version < LAST_SUPPORTED_VERSION {
299 return Err(Error::InvalidConfiguration(format!(
300 "Unsupported database version {version}. Expected {CURRENT_VERSION}"
301 )))
302 }
303 let salt = salt.ok_or_else(|| Error::InvalidConfiguration("Missing salt value".into()))?;
304 Ok(Some(Metadata { version, columns, salt }))
305 }
306
307 pub fn is_valid(&self) -> bool {
308 for option in self.columns.iter() {
309 if !option.is_valid() {
310 return false
311 }
312 }
313 true
314 }
315}
316
317impl Metadata {
318 pub fn columns_to_migrate(&self) -> std::collections::BTreeSet<u8> {
319 std::collections::BTreeSet::new()
320 }
321}