1use crate::Result;
7use redb::{
8 Database, MultimapTableDefinition, MultimapTableHandle, ReadTransaction, ReadableDatabase,
9 ReadableMultimapTable, ReadableTable, TableDefinition, TableError, TableHandle,
10 WriteTransaction,
11};
12use std::fmt;
13use std::marker::PhantomData;
14
15#[cfg(test)]
16mod tests;
17
18#[derive(Debug)]
20pub enum DbCopyError {
21 DestinationTablesExist(Vec<String>),
23
24 DestinationCheckFailed(String),
26
27 SourceTableOpenFailed(String),
29
30 DestinationTableOpenFailed(String),
32
33 TableCopyFailed(String),
35
36 TransactionFailed(String),
38
39 CommitFailed(String),
41}
42
43impl std::error::Error for DbCopyError {}
44
45impl fmt::Display for DbCopyError {
46 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47 match self {
48 DbCopyError::DestinationTablesExist(names) => {
49 write!(f, "Destination already contains: {}", names.join(", "))
50 }
51 DbCopyError::DestinationCheckFailed(msg) => {
52 write!(f, "Destination check failed: {}", msg)
53 }
54 DbCopyError::SourceTableOpenFailed(msg) => {
55 write!(f, "Source table open failed: {}", msg)
56 }
57 DbCopyError::DestinationTableOpenFailed(msg) => {
58 write!(f, "Destination table open failed: {}", msg)
59 }
60 DbCopyError::TableCopyFailed(msg) => write!(f, "Table copy failed: {}", msg),
61 DbCopyError::TransactionFailed(msg) => write!(f, "Transaction failed: {}", msg),
62 DbCopyError::CommitFailed(msg) => write!(f, "Commit failed: {}", msg),
63 }
64 }
65}
66
67enum CopyKind {
68 Table,
69 Multimap,
70}
71
72impl fmt::Display for CopyKind {
73 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74 match self {
75 CopyKind::Table => write!(f, "table"),
76 CopyKind::Multimap => write!(f, "multimap table"),
77 }
78 }
79}
80
81trait CopyStep {
82 fn name(&self) -> &str;
83 fn kind(&self) -> CopyKind;
84 fn preflight(&self, destination: &ReadTransaction) -> std::result::Result<bool, TableError>;
85 fn copy(
86 &self,
87 source: &ReadTransaction,
88 destination: &mut WriteTransaction,
89 ) -> std::result::Result<(), DbCopyError>;
90
91 fn display_name(&self) -> String {
92 format!("{} {}", self.kind(), self.name())
93 }
94}
95
96#[derive(Default)]
98pub struct CopyPlan {
99 steps: Vec<Box<dyn CopyStep>>,
100}
101
102impl CopyPlan {
103 pub fn new() -> Self {
105 Self { steps: Vec::new() }
106 }
107
108 pub fn table<K: redb::Key + 'static, V: redb::Value + 'static>(
110 mut self,
111 table: TableDefinition<'_, K, V>,
112 ) -> Self {
113 self.steps.push(Box::new(TablePlan::new(table)));
114 self
115 }
116
117 pub fn multimap<K: redb::Key + 'static, V: redb::Key + 'static>(
119 mut self,
120 table: MultimapTableDefinition<'_, K, V>,
121 ) -> Self {
122 self.steps.push(Box::new(MultimapPlan::new(table)));
123 self
124 }
125}
126
127pub fn copy_database(source: &Database, destination: &Database, plan: &CopyPlan) -> Result<()> {
129 let source_read = source
130 .begin_read()
131 .map_err(|err| DbCopyError::TransactionFailed(format!("source read: {}", err)))?;
132 let destination_read = destination
133 .begin_read()
134 .map_err(|err| DbCopyError::TransactionFailed(format!("destination read: {}", err)))?;
135
136 let mut conflicts = Vec::new();
137 for step in &plan.steps {
138 match step.preflight(&destination_read) {
139 Ok(true) => conflicts.push(step.display_name()),
140 Ok(false) => {}
141 Err(err) => {
142 return Err(DbCopyError::DestinationCheckFailed(format!(
143 "{}: {}",
144 step.display_name(),
145 err
146 ))
147 .into())
148 }
149 }
150 }
151
152 if !conflicts.is_empty() {
153 return Err(DbCopyError::DestinationTablesExist(conflicts).into());
154 }
155
156 drop(destination_read);
157
158 let mut destination_write = destination
159 .begin_write()
160 .map_err(|err| DbCopyError::TransactionFailed(format!("destination write: {}", err)))?;
161
162 for step in &plan.steps {
163 step.copy(&source_read, &mut destination_write)?;
164 }
165
166 destination_write
167 .commit()
168 .map_err(|err| DbCopyError::CommitFailed(err.to_string()))?;
169
170 Ok(())
171}
172
173struct TablePlan<K: redb::Key + 'static, V: redb::Value + 'static> {
174 name: String,
175 _key: PhantomData<K>,
176 _value: PhantomData<V>,
177}
178
179impl<K: redb::Key + 'static, V: redb::Value + 'static> TablePlan<K, V> {
180 fn new(table: TableDefinition<'_, K, V>) -> Self {
181 Self {
182 name: table.name().to_string(),
183 _key: PhantomData,
184 _value: PhantomData,
185 }
186 }
187
188 fn definition(&self) -> TableDefinition<'_, K, V> {
189 TableDefinition::new(self.name.as_str())
190 }
191}
192
193impl<K: redb::Key + 'static, V: redb::Value + 'static> CopyStep for TablePlan<K, V> {
194 fn name(&self) -> &str {
195 &self.name
196 }
197
198 fn kind(&self) -> CopyKind {
199 CopyKind::Table
200 }
201
202 fn preflight(&self, destination: &ReadTransaction) -> std::result::Result<bool, TableError> {
203 match destination.open_table(self.definition()) {
204 Ok(_) => Ok(true),
205 Err(TableError::TableDoesNotExist(_)) => Ok(false),
206 Err(err) => Err(err),
207 }
208 }
209
210 fn copy(
211 &self,
212 source: &ReadTransaction,
213 destination: &mut WriteTransaction,
214 ) -> std::result::Result<(), DbCopyError> {
215 let source_table = source.open_table(self.definition()).map_err(|err| {
216 DbCopyError::SourceTableOpenFailed(format!("{}: {}", self.display_name(), err))
217 })?;
218 let mut destination_table = destination.open_table(self.definition()).map_err(|err| {
219 DbCopyError::DestinationTableOpenFailed(format!("{}: {}", self.display_name(), err))
220 })?;
221 let iter = source_table.iter().map_err(|err| {
222 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
223 })?;
224
225 for entry in iter {
226 let (key, value) = entry.map_err(|err| {
227 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
228 })?;
229 destination_table
230 .insert(key.value(), value.value())
231 .map_err(|err| {
232 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
233 })?;
234 }
235
236 Ok(())
237 }
238}
239
240struct MultimapPlan<K: redb::Key + 'static, V: redb::Key + 'static> {
241 name: String,
242 _key: PhantomData<K>,
243 _value: PhantomData<V>,
244}
245
246impl<K: redb::Key + 'static, V: redb::Key + 'static> MultimapPlan<K, V> {
247 fn new(table: MultimapTableDefinition<'_, K, V>) -> Self {
248 Self {
249 name: table.name().to_string(),
250 _key: PhantomData,
251 _value: PhantomData,
252 }
253 }
254
255 fn definition(&self) -> MultimapTableDefinition<'_, K, V> {
256 MultimapTableDefinition::new(self.name.as_str())
257 }
258}
259
260impl<K: redb::Key + 'static, V: redb::Key + 'static> CopyStep for MultimapPlan<K, V> {
261 fn name(&self) -> &str {
262 &self.name
263 }
264
265 fn kind(&self) -> CopyKind {
266 CopyKind::Multimap
267 }
268
269 fn preflight(&self, destination: &ReadTransaction) -> std::result::Result<bool, TableError> {
270 match destination.open_multimap_table(self.definition()) {
271 Ok(_) => Ok(true),
272 Err(TableError::TableDoesNotExist(_)) => Ok(false),
273 Err(err) => Err(err),
274 }
275 }
276
277 fn copy(
278 &self,
279 source: &ReadTransaction,
280 destination: &mut WriteTransaction,
281 ) -> std::result::Result<(), DbCopyError> {
282 let source_table = source
283 .open_multimap_table(self.definition())
284 .map_err(|err| {
285 DbCopyError::SourceTableOpenFailed(format!("{}: {}", self.display_name(), err))
286 })?;
287 let mut destination_table =
288 destination
289 .open_multimap_table(self.definition())
290 .map_err(|err| {
291 DbCopyError::DestinationTableOpenFailed(format!(
292 "{}: {}",
293 self.display_name(),
294 err
295 ))
296 })?;
297 let iter = source_table.iter().map_err(|err| {
298 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
299 })?;
300
301 for entry in iter {
302 let (key, values) = entry.map_err(|err| {
303 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
304 })?;
305 for value in values {
306 let value = value.map_err(|err| {
307 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
308 })?;
309 destination_table
310 .insert(key.value(), value.value())
311 .map_err(|err| {
312 DbCopyError::TableCopyFailed(format!("{}: {}", self.display_name(), err))
313 })?;
314 }
315 }
316
317 Ok(())
318 }
319}