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