1#![deny(missing_docs)]
2#![doc=include_str!("../README.md")]
3
4use std::collections::{HashMap, VecDeque};
5use std::fmt::{self, Display, Write as _};
6use std::io;
7use std::iter;
8use std::mem;
9use std::ops;
10use std::str;
11
12use anyhow::{anyhow, Context as _};
13use derive_deftly::{Deftly, define_derive_deftly};
14use derive_more::From;
15use easy_ext::ext;
16use hex_fmt::HexFmt;
17use itertools::{chain, izip, Itertools};
18use rusqlite::{Transaction, types::ValueRef};
19use thiserror::Error;
20
21mod text;
22use text::write_text;
23mod real;
24use real::write_real;
25
26#[cfg(test)]
27mod test;
28
29pub struct Archiver<W> {
45 w: W,
46 tables: VecDeque<TableInfo>
47}
48
49struct TableInfo {
50 name: String,
51 cols: Vec<String>,
52}
53
54pub struct TableArchiver<'a, W> {
56 a: &'a mut Archiver<W>,
57 t: TableInfo,
58}
59
60#[derive(Error, Debug)]
62pub enum Error {
63 #[error("{0}")]
65 Io(#[from] io::Error),
66
67 #[error("lack of support for these database contents: {0:#}")]
69 Unsupported(anyhow::Error),
70
71 #[error("database operation failed: {0:#}")]
75 Db(anyhow::Error),
76
77 #[error("internal error: {0:#}")]
79 Internal(anyhow::Error),
80}
81
82type E = Error;
83
84impl<W: io::Write> Archiver<W> {
85 pub fn start<S: Into<String>>(
96 dbt: &Transaction,
97 mut w: W,
98 table_names: impl IntoIterator<Item = S>,
99 ) -> Result<Self, Error> {
100 let mut tables = VecDeque::new();
101
102 write!(w, include_str!("header.sql"))?;
103
104 let user_version: i64 = dbt.query_row(
105 r#" PRAGMA user_version "#, [],
106 |row| row.get(0)
107 )
108 .context("execute user_version access pragma").map_err(E::Db)?;
109
110 write!(w, "-- PRAGMA user_version = {user_version};\n")?;
111
112 let encoding: String = dbt.query_row(
113 r#" PRAGMA encoding "#, [],
114 |row| row.get(0)
115 )
116 .context("execute encoding access pragma").map_err(E::Db)?;
117
118 const EXPECTED_ENCODING: &str = "UTF-8";
119 if &encoding != EXPECTED_ENCODING {
120 return Err(E::Unsupported(anyhow!(
121 "database encoding is {encoding:?}, only {EXPECTED_ENCODING:?} is supported"
122 )));
123 }
124
125 let mut schema_stmt = dbt.prepare(
126 r#" SELECT sql FROM 'SQLITE_SCHEMA'
127 WHERE type = 'table' AND name = ? "#
128 ).context("prepare schema access query").map_err(E::Db)?;
129
130 for name in table_names {
131 let name: String = name.into();
132
133 let sql: String = schema_stmt.query_row(
134 [&name],
135 |row| Ok(row.get(0)),
136 )
137 .context("execute schema access query").map_err(E::Db)?
138 .context("obtain schema text from row").map_err(E::Db)?;
139
140 write!(w, "{};\n", sql)?;
141
142 let pragma = format!(r#" PRAGMA table_xinfo('{name}') "#);
143
144 let mut cols_stmt = dbt.prepare({
145 assert!(! name.contains(|c| c=='\'' || c=='\0'));
146 &pragma
147 }).context("prepare PRAGMA table_inf query").map_err(E::Db)?;
148
149 let cols = cols_stmt.query([])
150 .context("execute PRAGMA table_xinfo").map_err(E::Db)?
151 .mapped(|row| row.get("name"))
152 .collect::<Result<Vec<String>, _>>()
153 .context("read/convert PRAGMA table_xinfo rows")
154 .map_err(E::Db)?;
155
156 tables.push_back(TableInfo {
157 name,
158 cols,
159 });
160 }
161
162 let self_ = Archiver {
163 w,
164 tables,
165 };
166 Ok(self_)
167 }
168
169 pub fn start_table(&mut self, name: &str)
171 -> Result<TableArchiver<'_, W>, E>
172 {
173 let t = self.tables.pop_front()
174 .ok_or_else(|| internal_error(
175 anyhow!("start_table called too many times")
176 ))?;
177
178 if t.name != name {
179 return Err(internal_error(anyhow!(
180 "expected start_table({}), got start_table({name})",
181 t.name,
182 )));
183 }
184
185 Ok(TableArchiver {
186 a: self,
187 t,
188 })
189 }
190
191 pub fn finish(self) -> Result<(), E> {
195 self.finish_with_writer()?;
196 Ok(())
197 }
198
199 pub fn finish_with_writer(mut self) -> Result<W, E> {
203 if ! self.tables.is_empty() {
204 let e = anyhow!(
205 "tables unprocessed at finish! {:?}",
206 self.tables.iter().map(|ti| &ti.name).collect_vec()
207 );
208 return Err(internal_error(e));
209 }
210
211 write!(self.w, "COMMIT;\n")?;
212 self.w.flush()?;
213 Ok(self.w)
214 }
215
216 pub fn writer_mut(&mut self) -> &mut W {
220 &mut self.w
221 }
222}
223
224pub trait RowLike {
226 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef<'_>>;
228
229 fn check_max_len(&self, l: usize) -> anyhow::Result<()>;
237}
238
239impl RowLike for rusqlite::Row<'_> {
240 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef<'_>> {
241 self.get_ref(n)
242 }
243 fn check_max_len(&self, l: usize) -> anyhow::Result<()> {
244 match self.get_ref(l) {
245 Err(rusqlite::Error::InvalidColumnIndex { .. }) => Ok(()),
246 Err(other) => Err(
247 anyhow::Error::from(other) .context(
250 "get out of range column failed in an unexpected way!"
251 )),
252 Ok(_) => Err(anyhow!(
253 "get out of range column succeeded!"
254 )),
255 }
256 }
257}
258
259impl RowLike for HashMap<&str, ValueRef<'_>> {
260 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef<'_>> {
261 self.get(n)
262 .copied()
263 .ok_or_else(|| rusqlite::Error::InvalidColumnName(n.into()))
264 }
265 fn check_max_len(&self, l: usize) -> anyhow::Result<()> {
266 if self.len() <= l {
267 Ok(())
268 } else {
269 Err(anyhow!("row has {} rows, expected at most {l}", self.len()))
270 }
271 }
272}
273
274impl<W: io::Write> TableArchiver<'_, W> {
275 pub fn write_row(
282 &mut self,
283 row: &impl RowLike,
284 ) -> Result<(), Error> {
285 let mut w = &mut self.a.w;
286 let t = &self.t;
287 write!(w, "INSERT INTO {} VALUES (", t.name)?;
288
289 row.check_max_len(t.cols.len()).map_err(internal_error)?;
290
291 for (delim, col) in izip!(
292 chain!([""], iter::repeat(",")),
293 &t.cols,
294 ) {
295 write!(w, "{delim}")?;
296 let v = row.get_by_name(col)
297 .with_context(|| format!("table {:?}", t.name))
298 .context("fetch data row")
299 .map_err(E::Db)?;
300
301 write_value(&mut w, v)?;
302 }
303
304 write!(w, ");\n")?;
305
306 Ok(())
307 }
308
309 pub fn writer_mut(&mut self) -> &mut W {
313 &mut self.a.w
314 }
315}
316
317pub fn write_value(mut w: impl io::Write, v: ValueRef<'_>) -> Result<(), E> {
324 use ValueRef as V;
325 match v {
326 V::Null => write!(w, "NULL")?,
327 V::Integer(i) => write!(w, "{i}")?,
328 V::Real(v) => write_real(w, v)?,
329 V::Blob(b) => write!(w, "x'{}'", HexFmt(b))?,
330 V::Text(t) => write_text(w, t)?,
331 };
332 Ok(())
333}
334
335fn internal_error(ae: anyhow::Error) -> E {
336 Error::Internal(ae)
337}