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 encoding: String = dbt.query_row(
105 r#" PRAGMA encoding "#, [],
106 |row| row.get(0)
107 )
108 .context("execute encoding access pragma").map_err(E::Db)?;
109
110 const EXPECTED_ENCODING: &str = "UTF-8";
111 if &encoding != EXPECTED_ENCODING {
112 return Err(E::Unsupported(anyhow!(
113 "database encoding is {encoding:?}, only {EXPECTED_ENCODING:?} is supported"
114 )));
115 }
116
117 let mut schema_stmt = dbt.prepare(
118 r#" SELECT sql FROM 'SQLITE_SCHEMA'
119 WHERE type = 'table' AND name = ? "#
120 ).context("prepare schema access query").map_err(E::Db)?;
121
122 for name in table_names {
123 let name: String = name.into();
124
125 let sql: String = schema_stmt.query_row(
126 [&name],
127 |row| Ok(row.get(0)),
128 )
129 .context("execute schema access query").map_err(E::Db)?
130 .context("obtain schema text from row").map_err(E::Db)?;
131
132 write!(w, "{};\n", sql)?;
133
134 let pragma = format!(r#" PRAGMA table_xinfo('{name}') "#);
135
136 let mut cols_stmt = dbt.prepare({
137 assert!(! name.contains(|c| c=='\'' || c=='\0'));
138 &pragma
139 }).context("prepare PRAGMA table_inf query").map_err(E::Db)?;
140
141 let cols = cols_stmt.query([])
142 .context("execute PRAGMA table_xinfo").map_err(E::Db)?
143 .mapped(|row| row.get("name"))
144 .collect::<Result<Vec<String>, _>>()
145 .context("read/convert PRAGMA table_xinfo rows")
146 .map_err(E::Db)?;
147
148 tables.push_back(TableInfo {
149 name,
150 cols,
151 });
152 }
153
154 let self_ = Archiver {
155 w,
156 tables,
157 };
158 Ok(self_)
159 }
160
161 pub fn start_table(&mut self, name: &str) -> Result<TableArchiver<W>, E> {
163 let t = self.tables.pop_front()
164 .ok_or_else(|| internal_error(
165 anyhow!("start_table called too many times")
166 ))?;
167
168 if t.name != name {
169 return Err(internal_error(anyhow!(
170 "expected start_table({}), got start_table({name})",
171 t.name,
172 )));
173 }
174
175 Ok(TableArchiver {
176 a: self,
177 t,
178 })
179 }
180
181 pub fn finish(self) -> Result<(), E> {
185 self.finish_with_writer()?;
186 Ok(())
187 }
188
189 pub fn finish_with_writer(mut self) -> Result<W, E> {
193 if ! self.tables.is_empty() {
194 let e = anyhow!(
195 "tables unprocessed at finish! {:?}",
196 self.tables.iter().map(|ti| &ti.name).collect_vec()
197 );
198 return Err(internal_error(e));
199 }
200
201 write!(self.w, "COMMIT;\n")?;
202 self.w.flush()?;
203 Ok(self.w)
204 }
205
206 pub fn writer_mut(&mut self) -> &mut W {
210 &mut self.w
211 }
212}
213
214pub trait RowLike {
216 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef>;
218
219 fn check_max_len(&self, l: usize) -> anyhow::Result<()>;
227}
228
229impl RowLike for rusqlite::Row<'_> {
230 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef> {
231 self.get_ref(n)
232 }
233 fn check_max_len(&self, l: usize) -> anyhow::Result<()> {
234 match self.get_ref(l) {
235 Err(rusqlite::Error::InvalidColumnIndex { .. }) => Ok(()),
236 Err(other) => Err(
237 anyhow::Error::from(other) .context(
240 "get out of range column failed in an unexpected way!"
241 )),
242 Ok(_) => Err(anyhow!(
243 "get out of range column succeeded!"
244 )),
245 }
246 }
247}
248
249impl RowLike for HashMap<&str, ValueRef<'_>> {
250 fn get_by_name(&self, n: &str) -> rusqlite::Result<ValueRef> {
251 self.get(n)
252 .copied()
253 .ok_or_else(|| rusqlite::Error::InvalidColumnName(n.into()))
254 }
255 fn check_max_len(&self, l: usize) -> anyhow::Result<()> {
256 if self.len() <= l {
257 Ok(())
258 } else {
259 Err(anyhow!("row has {} rows, expected at most {l}", self.len()))
260 }
261 }
262}
263
264impl<W: io::Write> TableArchiver<'_, W> {
265 pub fn write_row(
272 &mut self,
273 row: &impl RowLike,
274 ) -> Result<(), Error> {
275 let mut w = &mut self.a.w;
276 let t = &self.t;
277 write!(w, "INSERT INTO {} VALUES (", t.name)?;
278
279 row.check_max_len(t.cols.len()).map_err(internal_error)?;
280
281 for (delim, col) in izip!(
282 chain!([""], iter::repeat(",")),
283 &t.cols,
284 ) {
285 write!(w, "{delim}")?;
286 let v = row.get_by_name(col)
287 .with_context(|| format!("table {:?}", t.name))
288 .context("fetch data row")
289 .map_err(E::Db)?;
290
291 write_value(&mut w, v)?;
292 }
293
294 write!(w, ");\n")?;
295
296 Ok(())
297 }
298
299 pub fn writer_mut(&mut self) -> &mut W {
303 &mut self.a.w
304 }
305}
306
307pub fn write_value(mut w: impl io::Write, v: ValueRef<'_>) -> Result<(), E> {
314 use ValueRef as V;
315 match v {
316 V::Null => write!(w, "NULL")?,
317 V::Integer(i) => write!(w, "{i}")?,
318 V::Real(v) => write_real(w, v)?,
319 V::Blob(b) => write!(w, "x'{}'", HexFmt(b))?,
320 V::Text(t) => write_text(w, t)?,
321 };
322 Ok(())
323}
324
325fn internal_error(ae: anyhow::Error) -> E {
326 Error::Internal(ae)
327}