iroh_bytes/store/fs/
migrate_redb_v1_v2.rs1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4use redb_v1::ReadableTable;
5use tempfile::NamedTempFile;
6use tracing::info;
7
8pub fn run(source: impl AsRef<Path>) -> Result<redb::Database> {
9 let source = source.as_ref();
10 let dir = source.parent().expect("database is not in root");
11 let target = NamedTempFile::with_prefix_in("blobs.db.migrate", dir)?;
13 let target = target.into_temp_path();
14 info!("migrate {} to {}", source.display(), target.display());
15 let old_db = redb_v1::Database::open(source)?;
16 let new_db = redb::Database::create(&target)?;
17
18 let rtx = old_db.begin_read()?;
19 let wtx = new_db.begin_write()?;
20
21 {
22 let old_blobs = rtx.open_table(old::BLOBS_TABLE)?;
23 let mut new_blobs = wtx.open_table(new::BLOBS_TABLE)?;
24 let len = old_blobs.len()?;
25 info!("migrate blobs table ({len} rows)");
26 for (i, entry) in old_blobs.iter()?.enumerate() {
27 let (key, value) = entry?;
28 let key: crate::Hash = key.value().into();
29 let value = value.value();
30 if i > 0 && i % 1000 == 0 {
31 info!(" row {i:>6} of {len}");
32 }
33 new_blobs.insert(key, value)?;
34 }
35 info!("migrate blobs table done");
36 let old_tags = rtx.open_table(old::TAGS_TABLE)?;
37 let mut new_tags = wtx.open_table(new::TAGS_TABLE)?;
38 let len = old_tags.len()?;
39 info!("migrate tags table ({len} rows)");
40 for (i, entry) in old_tags.iter()?.enumerate() {
41 let (key, value) = entry?;
42 let key = key.value();
43 let value: crate::HashAndFormat = value.value().into();
44 if i > 0 && i % 1000 == 0 {
45 info!(" row {i:>6} of {len}");
46 }
47 new_tags.insert(key, value)?;
48 }
49 info!("migrate tags table done");
50 let old_inline_data = rtx.open_table(old::INLINE_DATA_TABLE)?;
51 let mut new_inline_data = wtx.open_table(new::INLINE_DATA_TABLE)?;
52 let len = old_inline_data.len()?;
53 info!("migrate inline data table ({len} rows)");
54 for (i, entry) in old_inline_data.iter()?.enumerate() {
55 let (key, value) = entry?;
56 let key: crate::Hash = key.value().into();
57 let value = value.value();
58 if i > 0 && i % 1000 == 0 {
59 info!(" row {i:>6} of {len}");
60 }
61 new_inline_data.insert(key, value)?;
62 }
63 info!("migrate inline data table done");
64 let old_inline_outboard = rtx.open_table(old::INLINE_OUTBOARD_TABLE)?;
65 let mut new_inline_outboard = wtx.open_table(new::INLINE_OUTBOARD_TABLE)?;
66 let len = old_inline_outboard.len()?;
67 info!("migrate inline outboard table ({len} rows)");
68 for (i, entry) in old_inline_outboard.iter()?.enumerate() {
69 let (key, value) = entry?;
70 let key: crate::Hash = key.value().into();
71 let value = value.value();
72 if i > 0 && i % 1000 == 0 {
73 info!(" row {i:>6} of {len}");
74 }
75 new_inline_outboard.insert(key, value)?;
76 }
77 info!("migrate inline outboard table done");
78 }
79
80 wtx.commit()?;
81 drop(rtx);
82 drop(old_db);
83 drop(new_db);
84
85 let backup_path: PathBuf = {
86 let mut p = source.to_owned().into_os_string();
87 p.push(".backup-redb-v1");
88 p.into()
89 };
90 info!("rename {} to {}", source.display(), backup_path.display());
91 std::fs::rename(source, &backup_path)?;
92 info!("rename {} to {}", target.display(), source.display());
93 target.persist_noclobber(source)?;
94 info!("opening migrated database from {}", source.display());
95 let db = redb::Database::open(source)?;
96 Ok(db)
97}
98
99mod new {
100 pub(super) use super::super::tables::*;
101}
102
103mod old {
104 use super::super::EntryState;
105 use crate::util::Tag;
106 use bytes::Bytes;
107 use iroh_base::hash::BlobFormat;
108 use postcard::experimental::max_size::MaxSize;
109 use redb_v1::{RedbKey, RedbValue, TableDefinition, TypeName};
110 use serde::{Deserialize, Deserializer, Serialize, Serializer};
111 use smallvec::SmallVec;
112
113 pub const BLOBS_TABLE: TableDefinition<Hash, EntryState> = TableDefinition::new("blobs-0");
114
115 pub const TAGS_TABLE: TableDefinition<Tag, HashAndFormat> = TableDefinition::new("tags-0");
116
117 pub const INLINE_DATA_TABLE: TableDefinition<Hash, &[u8]> =
118 TableDefinition::new("inline-data-0");
119
120 pub const INLINE_OUTBOARD_TABLE: TableDefinition<Hash, &[u8]> =
121 TableDefinition::new("inline-outboard-0");
122
123 impl redb_v1::RedbValue for EntryState {
124 type SelfType<'a> = EntryState;
125
126 type AsBytes<'a> = SmallVec<[u8; 128]>;
127
128 fn fixed_width() -> Option<usize> {
129 None
130 }
131
132 fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
133 where
134 Self: 'a,
135 {
136 postcard::from_bytes(data).unwrap()
137 }
138
139 fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
140 where
141 Self: 'a,
142 Self: 'b,
143 {
144 postcard::to_extend(value, SmallVec::new()).unwrap()
145 }
146
147 fn type_name() -> TypeName {
148 TypeName::new("EntryState")
149 }
150 }
151
152 impl RedbValue for HashAndFormat {
153 type SelfType<'a> = Self;
154
155 type AsBytes<'a> = [u8; Self::POSTCARD_MAX_SIZE];
156
157 fn fixed_width() -> Option<usize> {
158 Some(Self::POSTCARD_MAX_SIZE)
159 }
160
161 fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
162 where
163 Self: 'a,
164 {
165 let t: &'a [u8; Self::POSTCARD_MAX_SIZE] = data.try_into().unwrap();
166 postcard::from_bytes(t.as_slice()).unwrap()
167 }
168
169 fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
170 where
171 Self: 'a,
172 Self: 'b,
173 {
174 let mut res = [0u8; 33];
175 postcard::to_slice(&value, &mut res).unwrap();
176 res
177 }
178
179 fn type_name() -> TypeName {
180 TypeName::new("iroh_base::HashAndFormat")
181 }
182 }
183
184 impl RedbValue for Tag {
185 type SelfType<'a> = Self;
186
187 type AsBytes<'a> = bytes::Bytes;
188
189 fn fixed_width() -> Option<usize> {
190 None
191 }
192
193 fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
194 where
195 Self: 'a,
196 {
197 Self(Bytes::copy_from_slice(data))
198 }
199
200 fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
201 where
202 Self: 'a,
203 Self: 'b,
204 {
205 value.0.clone()
206 }
207
208 fn type_name() -> TypeName {
209 TypeName::new("Tag")
210 }
211 }
212
213 impl RedbKey for Tag {
214 fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
215 data1.cmp(data2)
216 }
217 }
218
219 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
220 pub struct Hash([u8; 32]);
221
222 impl Serialize for Hash {
223 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
224 where
225 S: Serializer,
226 {
227 self.0.serialize(serializer)
228 }
229 }
230
231 impl<'de> Deserialize<'de> for Hash {
232 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
233 where
234 D: Deserializer<'de>,
235 {
236 let data: [u8; 32] = Deserialize::deserialize(deserializer)?;
237 Ok(Self(data))
238 }
239 }
240
241 impl MaxSize for Hash {
242 const POSTCARD_MAX_SIZE: usize = 32;
243 }
244
245 impl From<Hash> for crate::Hash {
246 fn from(value: Hash) -> Self {
247 value.0.into()
248 }
249 }
250
251 impl RedbValue for Hash {
252 type SelfType<'a> = Self;
253
254 type AsBytes<'a> = &'a [u8; 32];
255
256 fn fixed_width() -> Option<usize> {
257 Some(32)
258 }
259
260 fn from_bytes<'a>(data: &'a [u8]) -> Self::SelfType<'a>
261 where
262 Self: 'a,
263 {
264 let contents: &'a [u8; 32] = data.try_into().unwrap();
265 Hash(*contents)
266 }
267
268 fn as_bytes<'a, 'b: 'a>(value: &'a Self::SelfType<'b>) -> Self::AsBytes<'a>
269 where
270 Self: 'a,
271 Self: 'b,
272 {
273 &value.0
274 }
275
276 fn type_name() -> TypeName {
277 TypeName::new("iroh_base::Hash")
278 }
279 }
280
281 impl RedbKey for Hash {
282 fn compare(data1: &[u8], data2: &[u8]) -> std::cmp::Ordering {
283 data1.cmp(data2)
284 }
285 }
286
287 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, MaxSize)]
289 pub struct HashAndFormat {
290 pub hash: Hash,
292 pub format: BlobFormat,
294 }
295
296 impl From<HashAndFormat> for crate::HashAndFormat {
297 fn from(value: HashAndFormat) -> Self {
298 crate::HashAndFormat {
299 hash: value.hash.into(),
300 format: value.format,
301 }
302 }
303 }
304 impl Serialize for HashAndFormat {
305 fn serialize<S>(&self, serializer: S) -> std::result::Result<S::Ok, S::Error>
306 where
307 S: Serializer,
308 {
309 (self.hash, self.format).serialize(serializer)
310 }
311 }
312
313 impl<'de> Deserialize<'de> for HashAndFormat {
314 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
315 where
316 D: Deserializer<'de>,
317 {
318 let (hash, format) = <(Hash, BlobFormat)>::deserialize(deserializer)?;
319 Ok(Self { hash, format })
320 }
321 }
322}